<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 4.2.1">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/%E6%AD%A6%E6%B1%8932x32.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/%E6%AD%A6%E6%B1%8916x16.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib1/font-awesome/css/all.min.css">
  <link rel="stylesheet" href="/lib1/pace/pace-theme-center-simple.min.css">
  <script src="/lib1/pace/pace.min.js"></script>

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"example.com","root":"/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":true,"show_result":true,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
  </script>

  <meta property="og:type" content="website">
<meta property="og:title" content="Technological Blog">
<meta property="og:url" content="http://example.com/page/7/index.html">
<meta property="og:site_name" content="Technological Blog">
<meta property="og:locale" content="zh_CN">
<meta property="article:author" content="Li Yudong">
<meta name="twitter:card" content="summary">

<link rel="canonical" href="http://example.com/page/7/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : true,
    isPost : false,
    lang   : 'zh-CN'
  };
</script>

  <title>Technological Blog</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">Technological Blog</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">IT小白的成长之旅</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-about">

    <a href="/about/" rel="section"><i class="user fa-fw"></i>关于</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="tags fa-fw"></i>标签<span class="badge">18</span></a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="th fa-fw"></i>分类<span class="badge">14</span></a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="archive fa-fw"></i>归档<span class="badge">95</span></a>

  </li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div id="search-result">
  <div id="no-result">
    <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
  </div>
</div>

    </div>
  </div>

</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>

  <a href="https://github.com/tiarmor1" class="github-corner" title="Follow me on GitHub" aria-label="Follow me on GitHub" rel="noopener" target="_blank"><svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content index posts-expand">
            
      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/03/27/webpack/webpack%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/03/27/webpack/webpack%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/" class="post-title-link" itemprop="url">webpack性能优化</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-03-27 12:18:39" itemprop="dateCreated datePublished" datetime="2021-03-27T12:18:39+08:00">2021-03-27</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-04-02 15:43:37" itemprop="dateModified" datetime="2021-04-02T15:43:37+08:00">2021-04-02</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/webpack/" itemprop="url" rel="index"><span itemprop="name">webpack</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>承接之前的webpack的基本配置，这一篇文章主要说明下webpack的性能优化的方法。主要包括两部分：1、优化开发体验，即提升webpack打包时的速度；2、优化输出质量。，即缩小部署到服务器上的源码大小，尽量提升性能。</p>
<h1 id="Webpack优化实践"><a href="#Webpack优化实践" class="headerlink" title="Webpack优化实践"></a>Webpack优化实践</h1><h2 id="缩小文件范围Loader"><a href="#缩小文件范围Loader" class="headerlink" title="缩小文件范围Loader"></a>缩小文件范围Loader</h2><p>loader是一个消耗性能的大户，因此不要使用过多loader，同时缩小loader的查找范围。推荐使用：test、include、exclude来缩小处理范围。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">include: path.resolve(__dirname, <span class="string">"./src"</span>),</span><br></pre></td></tr></table></figure>

<h2 id="优化resolve配置"><a href="#优化resolve配置" class="headerlink" title="优化resolve配置"></a>优化resolve配置</h2><h3 id="优化resolve-modules配置"><a href="#优化resolve-modules配置" class="headerlink" title="优化resolve.modules配置"></a>优化resolve.modules配置</h3><p>resolve.modules⽤于配置webpack去哪些⽬录下寻找第三⽅模块。默认是在当前项⽬⽬录下的node_modules⾥⾯去找，如果没有找到，就会去上⼀级⽬录../node_modules找，再没有会去../../node_modules中找，以此类推，和Node.js的模块寻找机制很类似。如果我们的第三⽅模块都安装在了项⽬根⽬录下，就可以直接指明这个路径</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports=&#123;</span><br><span class="line"> resolve:&#123;</span><br><span class="line"> modules: [path.resolve(__dirname, <span class="string">"./node_modules"</span>)]</span><br><span class="line"> &#125; &#125;</span><br></pre></td></tr></table></figure>

<h3 id="优化resolve-alias配置"><a href="#优化resolve-alias配置" class="headerlink" title="优化resolve.alias配置"></a>优化<strong>resolve.alias</strong>配置</h3><p>resolve.alias配置通过别名来将原导⼊路径映射成⼀个新的导⼊路径，拿react为例，我们引⼊的react库，⼀般存在两套代码。</p>
<p>1、CJS：采用commonJS规范的模块化代码；2、UMD：已经打包好的完整代码，没有采⽤模块化，可以直接执⾏。</p>
<p>默认情况下，webpack会从⼊⼝⽂件./node_modules/bin/react/index开始递归解析和处理依赖的⽂件。我们可以直接指定⽂件，避免这处的耗时。其实就是减少查找过程、起别名。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">alias: &#123;</span><br><span class="line">    <span class="string">"@"</span>: path.join(__dirname, <span class="string">"./pages"</span>),</span><br><span class="line">        react: path.resolve(</span><br><span class="line">            __dirname,</span><br><span class="line">            <span class="string">"./node_modules/react/umd/react.production.min.js"</span></span><br><span class="line">        ),</span><br><span class="line">            <span class="string">"react-dom"</span>: path.resolve(</span><br><span class="line">                __dirname,</span><br><span class="line">                <span class="string">"./node_modules/react-dom/umd/react-dom.production.min.js"</span></span><br><span class="line">            )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="优化resolve-extensions配置"><a href="#优化resolve-extensions配置" class="headerlink" title="优化resolve.extensions配置"></a>优化<strong>resolve.extensions</strong>配置</h3><p>resolve.extensions在导⼊语句没带⽂件后缀时，webpack会⾃动带上后缀后，去尝试查找⽂件是否存在。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">extensions:[<span class="string">'.js'</span>,<span class="string">'.json'</span>,<span class="string">'.jsx'</span>,<span class="string">'.ts'</span>]</span><br><span class="line"><span class="comment">//后缀尝试列表尽量的小、导入语句尽量的带上后缀</span></span><br></pre></td></tr></table></figure>

<h2 id="基于CDN的优化"><a href="#基于CDN的优化" class="headerlink" title="基于CDN的优化"></a>基于CDN的优化</h2><h3 id="使⽤externals优化cdn静态资源"><a href="#使⽤externals优化cdn静态资源" class="headerlink" title="使⽤externals优化cdn静态资源"></a>使⽤<strong>externals</strong>优化<strong>cdn</strong>静态资源</h3><p>我们可以将⼀些JS⽂件存储在 CDN 上(减少 Webpack 打包出来的 js 体积)，在 index.html 中通过标签引⼊。我们希望在使⽤时，仍然可以通过 import 的⽅式去引⽤(如 import $ from ‘jquery’ )，并且希望webpack 不会对其进⾏打包，此时就可以配置 externals 。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//webpack.config.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> externals: &#123;</span><br><span class="line">     <span class="comment">//jquery通过script引⼊之后，全局中即有了 jQuery 变量</span></span><br><span class="line">     <span class="string">'jquery'</span>: <span class="string">'jQuery'</span></span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>为什么全局引入了还要webpack引入：因为你在写代码的时候不引入jQuery模块，就会报错。html模板上添加CDN资源，配置上面externals忽略CDN资源。但此时，打包后的资源就不需要再打包进去了。</p>
<h3 id="使⽤静态资源路径publicPath-CDN"><a href="#使⽤静态资源路径publicPath-CDN" class="headerlink" title="使⽤静态资源路径publicPath(CDN)"></a>使⽤静态资源路径publicPath(CDN)</h3><p>CDN通过将资源部署到世界各地，使得⽤户可以就近访问资源，加快访问速度。要接⼊CDN，需要把⽹⻚的静态资源上传到CDN服务上，在访问这些资源时，使⽤CDN服务提供的URL</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">##webpack.config.js</span><br><span class="line">output:&#123;</span><br><span class="line"> publicPath: <span class="string">'//cdnURL.com'</span>, <span class="comment">//指定存放JS⽂件的CDN地址</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="CSS、HTML的处理"><a href="#CSS、HTML的处理" class="headerlink" title="CSS、HTML的处理"></a>CSS、HTML的处理</h2><h3 id="CSS处理"><a href="#CSS处理" class="headerlink" title="CSS处理"></a>CSS处理</h3><p>1、使⽤less或者sass当做css技术栈；2、使⽤postcss为样式⾃动补⻬浏览器前缀；</p>
<p>如果不做抽取配置，我们的 css 是直接打包进 js ⾥⾯的，我们希望能单独⽣成 css ⽂件。 因为单独⽣成css,css可以和js并⾏下载，提⾼⻚⾯加载效率</p>
<h4 id="借助MiniCssExtractPlugin-完成抽离css"><a href="#借助MiniCssExtractPlugin-完成抽离css" class="headerlink" title="借助MiniCssExtractPlugin 完成抽离css"></a>借助<strong>MiniCssExtractPlugin</strong> 完成抽离<strong>css</strong></h4><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> MiniCssExtractPlugin = <span class="built_in">require</span>(<span class="string">"mini-css-extract-plugin"</span>);</span><br><span class="line"> &#123;</span><br><span class="line">     test: <span class="regexp">/\.scss$/</span>,</span><br><span class="line">     use: [</span><br><span class="line"> <span class="comment">// "style-loader", // 不再需要style-loader，⽤MiniCssExtractPlugin.loader代替</span></span><br><span class="line"> MiniCssExtractPlugin.loader,</span><br><span class="line"> <span class="string">"css-loader"</span>, <span class="comment">// 编译css</span></span><br><span class="line"> <span class="string">"postcss-loader"</span>,</span><br><span class="line"> <span class="string">"sass-loader"</span> <span class="comment">// 编译scss</span></span><br><span class="line">     ]</span><br><span class="line"> &#125;,</span><br><span class="line">plugins: [</span><br><span class="line">    <span class="keyword">new</span> MiniCssExtractPlugin(&#123;</span><br><span class="line">        filename: <span class="string">"css/[name]_[contenthash:6].css"</span>,</span><br><span class="line">        chunkFilename: <span class="string">"[id].css"</span></span><br><span class="line">    &#125;)</span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<h4 id="压缩CSS"><a href="#压缩CSS" class="headerlink" title="压缩CSS"></a>压缩CSS</h4><p>借助 optimize-css-assets-webpack-plugin；借助cssnano</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> OptimizeCSSAssetsPlugin = <span class="built_in">require</span>(<span class="string">"optimize-css-assets-webpack-plugin"</span>);</span><br><span class="line"><span class="keyword">new</span> OptimizeCSSAssetsPlugin(&#123;</span><br><span class="line"> cssProcessor: <span class="built_in">require</span>(<span class="string">"cssnano"</span>), <span class="comment">//引⼊cssnano配置压缩选项</span></span><br><span class="line"> cssProcessorOptions: &#123;</span><br><span class="line"> discardComments: &#123; <span class="attr">removeAll</span>: <span class="literal">true</span> &#125;</span><br><span class="line"> &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h3 id="HTML处理"><a href="#HTML处理" class="headerlink" title="HTML处理"></a>HTML处理</h3><p>借助html-webpack-plugin</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> htmlWebpackPlugin(&#123;</span><br><span class="line">    title: <span class="string">"京东商城"</span>,</span><br><span class="line">    template: <span class="string">"./index.html"</span>,</span><br><span class="line">    filename: <span class="string">"index.html"</span>,</span><br><span class="line">    minify: &#123;</span><br><span class="line">        <span class="comment">// 压缩HTML⽂件</span></span><br><span class="line">        removeComments: <span class="literal">true</span>, <span class="comment">// 移除HTML中的注释</span></span><br><span class="line">        collapseWhitespace: <span class="literal">true</span>, <span class="comment">// 删除空⽩符与换⾏符</span></span><br><span class="line">        minifyCSS: <span class="literal">true</span> <span class="comment">// 压缩内联css</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;),</span><br></pre></td></tr></table></figure>

<h2 id="开发、生产模式区分"><a href="#开发、生产模式区分" class="headerlink" title="开发、生产模式区分"></a>开发、生产模式区分</h2><p>利用webpack-merge来进行合并，</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> merge = <span class="built_in">require</span>(<span class="string">"webpack-merge"</span>)</span><br><span class="line"><span class="keyword">const</span> commonConfig = <span class="built_in">require</span>(<span class="string">"./webpack.common.js"</span>)</span><br><span class="line"><span class="keyword">const</span> devConfig = &#123;</span><br><span class="line"> ...</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">module</span>.exports = merge(commonConfig,devConfig)</span><br><span class="line"><span class="comment">//package.js</span></span><br><span class="line"><span class="string">"scripts"</span>:&#123;</span><br><span class="line"> <span class="string">"dev"</span>:<span class="string">"webpack-dev-server --config ./build/webpack.dev.js"</span>,</span><br><span class="line"> <span class="string">"build"</span>:<span class="string">"webpack --config ./build/webpack.prod.js"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>借助cross-env；package⾥⾯配置命令脚本，传⼊参数；在webpack.confifig.js⾥拿到参数</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//外部传⼊的全局变量</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function">(<span class="params">env</span>)=&gt;</span>&#123;</span><br><span class="line"> <span class="keyword">if</span>(env &amp;&amp; env.production)&#123;</span><br><span class="line"> <span class="keyword">return</span> merge(commonConfig,prodConfig)</span><br><span class="line"> &#125;<span class="keyword">else</span>&#123;</span><br><span class="line"> <span class="keyword">return</span> merge(commonConfig,devConfig)</span><br><span class="line"> &#125; &#125;</span><br><span class="line"><span class="comment">//外部传⼊变量</span></span><br><span class="line">scripts:<span class="string">" --env.production"</span></span><br></pre></td></tr></table></figure>

<h2 id="tree-shaking"><a href="#tree-shaking" class="headerlink" title="tree-shaking"></a>tree-shaking</h2><p>webpack2.x开始⽀持 tree shaking概念，顾名思义，”摇树”，清除⽆⽤ css,js(Dead Code)。主要分为CSS tree shaking和JS tree shaking。</p>
<p>Dead Code ⼀般具有以下⼏个特征：1、代码不会被执⾏，不可到达；2、代码执⾏的结果不会被⽤到；3、代码只会影响死变量（只写不读）4、Js tree shaking只⽀持ES module的引⼊⽅式！！！！,</p>
<h3 id="CSS-tree-shaking"><a href="#CSS-tree-shaking" class="headerlink" title="CSS tree shaking"></a>CSS tree shaking</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">npm i glob-all purify-css purifycss-webpack --save-dev</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> PurifyCSS = <span class="built_in">require</span>(<span class="string">'purifycss-webpack'</span>)</span><br><span class="line"><span class="keyword">const</span> glob = <span class="built_in">require</span>(<span class="string">'glob-all'</span>)</span><br><span class="line">plugins:[</span><br><span class="line"> <span class="comment">// 清除⽆⽤ css</span></span><br><span class="line"> <span class="keyword">new</span> PurifyCSS(&#123;</span><br><span class="line"> paths: glob.sync([</span><br><span class="line"> <span class="comment">// 要做 CSS Tree Shaking 的路径⽂件</span></span><br><span class="line"> path.resolve(__dirname, <span class="string">'./src/*.html'</span>), <span class="comment">// 请注意，我们同样需要对 html ⽂件进⾏ tree shaking</span></span><br><span class="line"> path.resolve(__dirname, <span class="string">'./src/*.js'</span>)</span><br><span class="line"> ])</span><br><span class="line"> &#125;)</span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<h3 id="JS-tree-shaking"><a href="#JS-tree-shaking" class="headerlink" title="JS tree shaking"></a>JS tree shaking</h3><p>只⽀持import⽅式引⼊，不⽀持commonjs的⽅式引⼊</p>
<p>只要<strong>mode</strong>是<strong>production</strong>就会⽣效，<strong>develpoment</strong>的<strong>tree shaking</strong>是不⽣效的，因为<strong>webpack</strong>为了⽅便你的调试。可以查看打包后的代码注释以辨别是否⽣效。⽣产模式不需要配置，默认开启</p>
<h3 id="副作用"><a href="#副作用" class="headerlink" title="副作用"></a>副作用</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//package.json</span></span><br><span class="line"><span class="string">"sideEffects"</span>:<span class="literal">false</span> <span class="comment">//正常对所有模块进⾏tree shaking , 仅⽣产模式有效，需要配合usedExports</span></span><br><span class="line"><span class="comment">//或者在数组⾥⾯排除不需要tree shaking的模块</span></span><br><span class="line"><span class="string">"sideEffects"</span>:[<span class="string">'*.css'</span>,<span class="string">'@babel/polyfill'</span>]</span><br></pre></td></tr></table></figure>

<h2 id="代码分割code-Splitting"><a href="#代码分割code-Splitting" class="headerlink" title="代码分割code Splitting"></a>代码分割code Splitting</h2><p>单页面应用SPA：打包完后，所有⻚⾯只⽣成了⼀个bundle.js。1、代码体积变⼤，不利于下载；2、没有合理利⽤浏览器资源。因为浏览器可做多支持同时5个请求加载资源。</p>
<p>多⻚⾯应⽤MPA：如果多个⻚⾯引⼊了⼀些公共模块，那么可以把这些公共的模块抽离出来，单独打包。公共代码只需要下载⼀次就缓存起来了，避免了重复下载。</p>
<p>假如我们引⼊⼀个第三⽅的⼯具库，体积为1mb，⽽我们的业务逻辑代码也有1mb，那么打包出来的体积⼤⼩会在2mb。导致问题：1、体积⼤，加载时间⻓；2、业务逻辑会变化，第三⽅⼯具库不会，所以业务逻辑⼀变更，第三⽅⼯具库也要跟着变</p>
<p>其实code Splitting概念 与 webpack并没有直接的关系，只不过webpack中提供了⼀种更加⽅便的⽅法供我们实现代码分割，基于split-chunks-plugin，我们可以实现简单的代码分割，让浏览器能更快速地加载代码。 </p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">optimization: &#123;</span><br><span class="line">    splitChunks: &#123;</span><br><span class="line">        chunks: <span class="string">"all"</span>, <span class="comment">// 所有的 chunks 代码公共的部分分离出来成为⼀个单独的⽂件</span></span><br><span class="line">            <span class="comment">//即支持同步引入的模块，也支持异步引入的模块</span></span><br><span class="line">    &#125;,</span><br><span class="line">&#125;,</span><br><span class="line"><span class="comment">//optimization的配置如下：</span></span><br><span class="line">optimization: &#123;</span><br><span class="line"> splitChunks: &#123;</span><br><span class="line">     chunks: <span class="string">'async'</span>,<span class="comment">//对同步 initial，异步 async，所有的模块有效 all</span></span><br><span class="line">     minSize: <span class="number">30000</span>,<span class="comment">//最⼩尺⼨，当模块⼤于30kb</span></span><br><span class="line">     maxSize: <span class="number">0</span>,<span class="comment">//对模块进⾏⼆次分割时使⽤，不推荐使⽤</span></span><br><span class="line">     minChunks: <span class="number">1</span>,<span class="comment">//打包⽣成的chunk⽂件最少有⼏个chunk引⽤了这个模块</span></span><br><span class="line">     maxAsyncRequests: <span class="number">5</span>,<span class="comment">//最⼤异步请求数，默认5</span></span><br><span class="line">     maxInitialRequests: <span class="number">3</span>,<span class="comment">//最⼤初始化请求书，⼊⼝⽂件同步请求，默认3</span></span><br><span class="line">     automaticNameDelimiter: <span class="string">'-'</span>,<span class="comment">//打包分割符号</span></span><br><span class="line">     name: <span class="literal">true</span>,<span class="comment">//打包后的名称，除了布尔值，还可以接收⼀个函数function</span></span><br><span class="line">     cacheGroups: &#123;<span class="comment">//缓存组</span></span><br><span class="line">     vendors: &#123;</span><br><span class="line">         test: <span class="regexp">/[\\/]node_modules[\\/]/</span>,</span><br><span class="line">         name:<span class="string">"vendor"</span>, <span class="comment">// 要缓存的 分隔出来的 chunk 名称</span></span><br><span class="line">         priority: <span class="number">-10</span><span class="comment">//缓存组优先级 数字越⼤，优先级越⾼</span></span><br><span class="line">     &#125;,</span><br><span class="line"> other:&#123;</span><br><span class="line">     chunks: <span class="string">"initial"</span>, <span class="comment">// 必须三选⼀： "initial" | "all" | "async"(默认就是async)</span></span><br><span class="line">     test: <span class="regexp">/react|lodash/</span>, <span class="comment">// 正则规则验证，如果符合就提取 chunk,</span></span><br><span class="line">     name:<span class="string">"other"</span>,</span><br><span class="line">     minSize: <span class="number">30000</span>,</span><br><span class="line">     minChunks: <span class="number">1</span>,</span><br><span class="line"> &#125;,</span><br><span class="line"> <span class="keyword">default</span>: &#123;</span><br><span class="line">     minChunks: <span class="number">2</span>,</span><br><span class="line">     priority: <span class="number">-20</span>,</span><br><span class="line">     reuseExistingChunk: <span class="literal">true</span><span class="comment">//可设置是否重⽤该chunk</span></span><br><span class="line">     &#125;</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>

<h2 id="Scope-Hoisting"><a href="#Scope-Hoisting" class="headerlink" title="Scope Hoisting"></a><strong>Scope Hoisting</strong></h2><p>作⽤域提升（Scope Hoisting）是指 webpack 通过 ES6 语法的静态分析，分析出模块之间的依赖关系，尽可能地把模块放到同⼀个函数中。过配置 optimization：开启 Scope Hoisting，通过 Scope Hoisting 的功能可以让Webpack 打包出来的代码⽂件更⼩、运⾏的更快。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">    optimization: &#123;</span><br><span class="line">        concatenateModules: <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h2 id="使⽤⼯具量化"><a href="#使⽤⼯具量化" class="headerlink" title="使⽤⼯具量化"></a>使⽤⼯具量化</h2><p>1、speed-measure-webpack-plugin:可以测量各个插件和 loader 所花费的时间</p>
<p>2、webpack-bundle-analyzer:分析webpack打包后的模块依赖关系：vv</p>
<h2 id="DllPlugin插件打包第三⽅类库"><a href="#DllPlugin插件打包第三⽅类库" class="headerlink" title="DllPlugin插件打包第三⽅类库"></a><strong>DllPlugin</strong>插件打包第三⽅类库</h2><p>每次打包，所有的依赖都需要重新构建一次，但有些公共的框架不需要每次重新构建，这样我们就可以先对这些不怎么改变的框架代码进行缓存，这样我们构建一个缓存的动态信息库。每次构建并检查依赖时，如果已经有了对应的动态缓存，就不再次进行构建，而是直接从缓存中查找，就可以减少第二次与以后的缓存时间。</p>
<p>Dll动态链接库 其实就是做缓存，只会提升webpack打包的速度，但并不会减少生成的代码体积，因此只是减少了开发时候的体验。项⽬中引⼊了很多第三⽅库，这些库在很⻓的⼀段时间内，基本不会更新，打包的时候分开打包来提升打包速度，⽽DllPlugin动态链接库插件，其原理就是把⽹⻚依赖的基础模块抽离出来打包到<strong>dll</strong>⽂件中，当需要导⼊的模块存在于某个<strong>dll</strong>中时，这个模块不再被打包，⽽是去<strong>dll</strong>中获取。</p>
<p>webpack已经内置了对动态链接库的⽀持。1、DllPlugin:⽤于打包出⼀个个单独的动态链接库⽂件。2、DllReferencePlugin：⽤于在主要的配置⽂件中引⼊DllPlugin插件打包好的动态链接库⽂件</p>
<p>新建webpack.dll.confifig.js⽂件，打包基础模块；你会发现多了⼀个dll⽂件夹，⾥边有dll.js⽂件，这样我们就把我们的React这些已经单独打包了。1、dll⽂件包含了⼤量模块的代码，这些模块被存放在⼀个数组⾥。⽤数组的索引号为ID,通过变量将⾃⼰暴露在全局中，就可以在window.xxx访问到其中的模块。2、Manifest.json 描述了与其对应的dll.js包含了哪些模块，以及ID和路径。</p>
<p>接下来要如何去使用：大概是要给web项目构建介入动态链接库，需要完成以下步骤：</p>
<p>1、将⽹⻚依赖的基础模块抽离，打包到单独的动态链接库，⼀个动态链接库是可以包含多个模块的。</p>
<p>2、当需要导⼊的模块存在于某个动态链接库中时，不要再次打包，直接使⽤构建好的动态链接库即可。</p>
<p>3、⻚⾯依赖的所有动态链接库都需要被加载。</p>
<p>这里其实可以使用add-asset-html-webpack-plugin插件来实现添加使用它会将我们打包后的 dll.js ⽂件注⼊到我们⽣成的 index.html 中.在 webpack.base.confifig.js ⽂件中进⾏更改。</p>
<p>但其实上述的操作执行起来比较麻烦，在webpack5上面已经有了<strong>HardSourceWebpackPlugin</strong>插件来代替实现共功能。1、提供中间缓存的作⽤；2、⾸次构建没有太⼤的变化；3、第⼆次构建时间就会有较⼤的节省</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> HardSourceWebpackPlugin = <span class="built_in">require</span>(<span class="string">'hard-source-webpack-plugin'</span>)</span><br><span class="line"><span class="keyword">const</span> plugins = [</span><br><span class="line"> snew HardSourceWebpackPlugin()</span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<h2 id="使⽤happypack并发执⾏任务"><a href="#使⽤happypack并发执⾏任务" class="headerlink" title="使⽤happypack并发执⾏任务"></a>使⽤<strong>happypack</strong>并发执⾏任务</h2><p>运⾏在 Node.之上的Webpack是单线程模型的，也就是说Webpack需要⼀个⼀个地处理任务，不能同时处理多个任务。 <strong>Happy Pack</strong> 就能让Webpack做到这⼀点，它将任务分解给多个⼦进程去并发执⾏，⼦进程处理完后再将结果发送给主进程。从⽽发挥多核 CPU 电脑的威⼒。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">npm i -D happypack</span><br><span class="line"><span class="keyword">var</span> happyThreadPool = HappyPack.ThreadPool(&#123; <span class="attr">size</span>: <span class="number">5</span> &#125;);</span><br><span class="line"> <span class="comment">//const happyThreadPool = HappyPack.ThreadPool(&#123; size: os.cpus().length &#125;)</span></span><br><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line">rules: [</span><br><span class="line">    &#123;</span><br><span class="line">        test: <span class="regexp">/\.jsx?$/</span>,</span><br><span class="line">        exclude: <span class="regexp">/node_modules/</span>,</span><br><span class="line">        use: [</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="comment">// ⼀个loader对应⼀个id</span></span><br><span class="line">                loader: <span class="string">"happypack/loader?id=babel"</span></span><br><span class="line">            &#125;</span><br><span class="line">        ]</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">        test: <span class="regexp">/\.css$/</span>,</span><br><span class="line">        include: path.resolve(__dirname, <span class="string">"./src"</span>),</span><br><span class="line">        use: [<span class="string">"happypack/loader?id=css"</span>]</span><br><span class="line">    &#125;,</span><br><span class="line">]</span><br><span class="line"><span class="comment">//在plugins中增加</span></span><br><span class="line">plugins:[</span><br><span class="line">    <span class="keyword">new</span> HappyPack(&#123;</span><br><span class="line">        <span class="comment">// ⽤唯⼀的标识符id，来代表当前的HappyPack是⽤来处理⼀类特定的⽂件</span></span><br><span class="line">        id:<span class="string">'babel'</span>,</span><br><span class="line">        <span class="comment">// 如何处理.js⽂件，⽤法和Loader配置中⼀样</span></span><br><span class="line">        loaders:[<span class="string">'babel-loader?cacheDirectory'</span>]</span><br><span class="line">        threadPool: happyThreadPool,</span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="keyword">new</span> HappyPack(&#123;</span><br><span class="line">        id: <span class="string">"css"</span>,</span><br><span class="line">        loaders: [<span class="string">"style-loader"</span>, <span class="string">"css-loader"</span>]</span><br><span class="line">    &#125;),</span><br><span class="line">]</span><br></pre></td></tr></table></figure>


      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/03/21/webpack/webpack%E5%9F%BA%E7%A1%80%E9%85%8D%E7%BD%AE/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/03/21/webpack/webpack%E5%9F%BA%E7%A1%80%E9%85%8D%E7%BD%AE/" class="post-title-link" itemprop="url">webpack基础配置</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-03-21 12:18:39" itemprop="dateCreated datePublished" datetime="2021-03-21T12:18:39+08:00">2021-03-21</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:23:03" itemprop="dateModified" datetime="2021-11-03T16:23:03+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/webpack/" itemprop="url" rel="index"><span itemprop="name">webpack</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>由于之前项目是直接用vuecli构建的，使用webpack配置的情况较少，因此希望能够重新学习一下关于webpack方面的知识。这里主要还是以一个新手的角度来记述一下自己使用、学习webpack的过程。</p>
<p>webpack的定义：本质是一个module bundler(模块打包⼯具)，它会从⼊⼝模块出发，识别出源码中的模块化导⼊语句，递归地找出⼊⼝⽂件的所有依赖，将⼊⼝和其所有的依赖打包到⼀个单独的⽂件中；是⼯程化、⾃动化思想在前端开发中的体现。</p>
<p>为什么会有webpack：因为前端语言逻辑发展很快，但浏览器对其的支持不尽如人意，例如模块化的共享。因此我们需要一个打包工具，将自己的文件解析成浏览器能够识别、运行的程序。</p>
<h1 id="webpack配置"><a href="#webpack配置" class="headerlink" title="webpack配置"></a>webpack配置</h1><p>默认配置：支持JS模块和JSON模块；支持CommonJS、ES Moudul e、AMD等模块类型。</p>
<p>1、webpack执行构建会找webpack.config.js的配置及文件，否则会走默认配置。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//webpack是基于nodeJS的，因此要准寻CommonJS的规范，使用module.export导出</span></span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">"path"</span>);</span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">    <span class="comment">//上下文：项目打包的相对路径,很少去改变</span></span><br><span class="line">    context: process.cwd(),</span><br><span class="line">    <span class="comment">//入口 执行构建的入口 项目入口：支持字符串、数组、对象，</span></span><br><span class="line">    <span class="comment">//数组时会打包多个成为一个；对象才是多入口的,多入口一定要有多出口，将entry设置为对象，这样就会有多出口</span></span><br><span class="line">    entry: &#123;</span><br><span class="line">        index: <span class="string">"./src/index.js"</span>,</span><br><span class="line">        other: <span class="string">"./src/other.js"</span></span><br><span class="line">    &#125;</span><br><span class="line">    entry: [<span class="string">"./src/index.js"</span>, <span class="string">"./src/other.js"</span>]</span><br><span class="line">    entry: <span class="string">"./src/index.js"</span>,</span><br><span class="line">    <span class="comment">//出口：即打包后生成的bundle文件要放在哪里</span></span><br><span class="line">    output: &#123;</span><br><span class="line">        <span class="comment">//构建资源在哪，必须为绝对路径，因此用p  ath来处理</span></span><br><span class="line">        path: path.resolve(_dirname, <span class="string">"./dist"</span>), </span><br><span class="line">        <span class="comment">//构建的文件资源加什么 无论是多出口还是单出口，都推荐使用占位符</span></span><br><span class="line">        filename: <span class="string">"[name].js"</span>,</span><br><span class="line">        <span class="comment">//占位符有：hash、chunkhash、name、id</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">//构建模式： none production development 开发环境中，打包出的JS不会被压缩</span></span><br><span class="line">    <span class="comment">//设置mode可以自动触发webpack内置的函数，从而达到优化的效果</span></span><br><span class="line">    <span class="comment">//开发阶段的开启会有利于热更新的处理，识别哪个模块的变化</span></span><br><span class="line">    <span class="comment">//生产阶段的开启会有帮助于模块压缩，处理副作用等功能</span></span><br><span class="line">    mode: <span class="string">""</span>,</span><br><span class="line">    <span class="comment">//处理不认识的模块</span></span><br><span class="line">    <span class="built_in">module</span>: &#123;</span><br><span class="line">        rules: [</span><br><span class="line">            &#123;</span><br><span class="line">                test: <span class="regexp">/\.css$/</span>,</span><br><span class="line">                <span class="comment">//loader的执行顺序是从后往前</span></span><br><span class="line">                <span class="comment">//css-loader：把CSS模块的内容加入到JS的模块</span></span><br><span class="line">                <span class="comment">//style-loader：从JS中提取CSS的loader在HTML中创建</span></span><br><span class="line">                use: [<span class="string">"style-loader"</span>, <span class="string">"css-loader"</span>],</span><br><span class="line">            &#125;,</span><br><span class="line">        ],</span><br><span class="line">    &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>1 Chunk = 1Bundle；多个Chunk就会有多个Bundle，这样就是多入口与多出口的结构。只有entry为多键值对象时，才是多入口、多出口的文件。 chunk为代码块，bundle为资源文件，1个chunk可以由多个资源文件组成。</p>
<p>entry为数组时：webpack会⾃动⽣成另外⼀个⼊⼝模块，并将数组中的每个指定的模块加载进来，并将最后⼀个模块的module.exports作为⼊⼝模块的module.exports导出。</p>
<p>模块化开发：当前流行的单页面入口应用SPA，通过require、import引入其他的模块，并形成依赖，这样就不用进行多入口，但同样可以实现一样效果。</p>
<h2 id="loader"><a href="#loader" class="headerlink" title="loader"></a>loader</h2><p>Webpack 默认只⽀持.json 和 .js模块 不⽀持 不认识其他格式的模块。而此时就需要用到模块解析、模块转换器，⽤于把模块原内容按照需求转换成新内容，这些其他格式的模块处理就交给的是loader。⼀个loader只处理⼀件事情，且loader有执⾏顺序，执行顺序为从后往前。</p>
<p>常见的loader有：style-loader、css-loader、less-loader、sass-loader、ts-loader 将Ts转换成js、babel-loader转换ES6、7等js新特性语法、file-loader//处理图⽚、eslint-loader</p>
<h3 id="file-loader"><a href="#file-loader" class="headerlink" title="file-loader"></a>file-loader</h3><p>原理是把打包⼊⼝中识别出的资源模块，移动到输出⽬录，并且返回⼀个地址名称</p>
<p>所以我们什么时候⽤fifile-loader呢？场景：就是当我们需要模块，仅仅是从源代码挪移到打包⽬录，就可以使⽤file-loader来处理，txt，svg，csv，excel，图⽚资源啦等等</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>: &#123;</span><br><span class="line">    rules: [</span><br><span class="line">        &#123;</span><br><span class="line">            test: <span class="regexp">/\.(png|jpe?g|gif)$/</span>,</span><br><span class="line">            <span class="comment">//use使⽤⼀个loader可以⽤对象，字符串，两个loader需要⽤数组</span></span><br><span class="line">            use: &#123;</span><br><span class="line">                loader: <span class="string">"file-loader"</span>,</span><br><span class="line">                <span class="comment">// options额外的配置，⽐如资源名称</span></span><br><span class="line">                options: &#123;</span><br><span class="line">                    <span class="comment">// placeholder 占位符 [name]⽼资源模块的名称</span></span><br><span class="line">                    <span class="comment">// [ext]⽼资源模块的后缀</span></span><br><span class="line">                    <span class="comment">// https://webpack.js.org/loaders/fileloader#placeholders</span></span><br><span class="line">                    name: <span class="string">"[name]_[hash].[ext]"</span>,</span><br><span class="line">                    <span class="comment">//打包后的存放位置</span></span><br><span class="line">                    outputPath: <span class="string">"images/"</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    ]</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure>

<h3 id="url-loader"><a href="#url-loader" class="headerlink" title="url-loader"></a>url-loader</h3><p>其实是file-loader加强版本，url-loader内部使⽤了fifile-loader,所以可以处理fifile-loader所有的事情，但是遇到jpg格式的模块，会把该图⽚转换成base64格式字符串，并打包到js⾥。对⼩体积的图⽚⽐较合适，⼤图⽚不合适。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>: &#123;</span><br><span class="line">    rules: [</span><br><span class="line">        &#123;</span><br><span class="line">            test: <span class="regexp">/\.(png|jpe?g|gif)$/</span>,</span><br><span class="line">            use: &#123;</span><br><span class="line">                loader: <span class="string">"url-loader"</span>,</span><br><span class="line">                options: &#123;</span><br><span class="line">                    name: <span class="string">"[name]_[hash].[ext]"</span>,</span><br><span class="line">                    outputPath: <span class="string">"images/"</span>,</span><br><span class="line">                    <span class="comment">//⼩于2048，才转换成base64</span></span><br><span class="line">                    limit: <span class="number">2048</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    ]</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure>

<h3 id="CSS相关loader"><a href="#CSS相关loader" class="headerlink" title="CSS相关loader"></a>CSS相关loader</h3><p>Css-loader 分析css模块之间的关系，并合成⼀个css；</p>
<p>Style-loader 会把css-loader⽣成的内容，以style挂载到⻚⾯的heade部分；</p>
<p>less-load 把less语法转换成css</p>
<h2 id="moudle"><a href="#moudle" class="headerlink" title="moudle"></a>moudle</h2><p>模块，在 Webpack ⾥⼀切皆模块，⼀个模块对应着⼀个⽂件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。当webpack处理到不认识的模块时，需要在webpack中的module处进⾏配置，当检测到是什么格式的模块，使⽤什么loader来处理。</p>
<h2 id="plugins"><a href="#plugins" class="headerlink" title="plugins"></a>plugins</h2><p>插件：对于webpack没有实现的功能，进行一个补充。作⽤于webpack打包整个过程，webpack的打包过程是有（⽣命周期概念）钩⼦，plugin 可以在webpack运⾏到某个阶段的时候，帮你做⼀些事情，类似于⽣命周期的概念，从而在 Webpack 构建流程中的特定时机注⼊扩展逻辑来改变构建结果或做你想要的事情。</p>
<p>loader是一个普通函数，plugin通常是一个类。 </p>
<h3 id="clean-webpack-plugin"><a href="#clean-webpack-plugin" class="headerlink" title="clean-webpack-plugin"></a><strong>clean-webpack-plugin</strong></h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; CleanWebpackPlugin &#125; = <span class="built_in">require</span>(<span class="string">"cleanwebpack-plugin"</span>);</span><br><span class="line">...</span><br><span class="line">plugins: [</span><br><span class="line"> <span class="keyword">new</span> CleanWebpackPlugin()</span><br><span class="line">]</span><br><span class="line"><span class="comment">//插件使用的方法比较简单，先引入再直接在plugins数组中添加上plugin即可。</span></span><br></pre></td></tr></table></figure>

<h3 id="HtmlWebpackPlugin"><a href="#HtmlWebpackPlugin" class="headerlink" title="HtmlWebpackPlugin"></a><strong>HtmlWebpackPlugin</strong></h3><p>htmlwebpackplugin会在打包结束后，⾃动⽣成⼀个html⽂件，并把打包⽣成的js模块引⼊到该html中。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">plugins: [</span><br><span class="line">    <span class="keyword">new</span> HtmlWebpackPlugin(&#123;</span><br><span class="line">        <span class="comment">//选择html模板模板⽂件路径，⽀持加载器，⽐如html!./index.html</span></span><br><span class="line">        template: <span class="string">"./src/index.html"</span>,</span><br><span class="line">        <span class="comment">//输出的 HTML ⽂件名，默认是 index.html, 也可以直接配置带有⼦⽬录。</span></span><br><span class="line">        filename: <span class="string">"index.html"</span>,</span><br><span class="line">    &#125;),</span><br><span class="line">],</span><br></pre></td></tr></table></figure>

<p><strong>HtmlWebpackPlugin</strong>的其他配置如下：</p>
<p>title: ⽤来⽣成⻚⾯的 title 元素</p>
<p>filename: 输出的 HTML ⽂件名，默认是 index.html, 也可以直接配置带有⼦⽬录。</p>
<p>template: 模板⽂件路径，⽀持加载器，⽐如html!./index.html</p>
<p>inject: true | ‘head’ | ‘body’ | false ,注⼊所有的资源到特定的 template 或者 templateContent 中，如果设置为true 或者 body，所有的 javascript 资源将被放置到 body 元素的底部，’head’ 将放置到 head 元素中。</p>
<p>favicon: 添加特定的 favicon 路径到输出的 HTML ⽂件中。</p>
<p>minify: {} | false , 传递 html-minifier 选项给 minify输出</p>
<p>hash: true | false, 如果为 true, 将添加⼀个唯⼀的webpack 编译 hash 到所有包含的脚本和 CSS ⽂件，对于解除cache 很有⽤。</p>
<p>cache: true | false，如果为 true, 这是默认值，仅仅在⽂件修改之后才会发布⽂件。</p>
<p>showErrors: true | false, 如果为 true, 这是默认值，错误信息会写⼊到 HTML ⻚⾯中</p>
<p>chunks: 允许只添加某些块 (⽐ 如，仅仅 unit test 块)</p>
<p>chunksSortMode: 允许控制块在添加到⻚⾯之前的排序⽅式，⽀持的值：’none’ | ‘default’ | {function}-default:’auto’</p>
<p>excludeChunks: 允许跳过某些块，(⽐如，跳过单元测试的块)</p>
<h2 id="devtool"><a href="#devtool" class="headerlink" title="devtool"></a>devtool</h2><p>当你开启development模式时，则会自动使用sourcemap；如果想要显式地开启sourcemap则可以加上devtool属性，设置为”source-map”。</p>
<p>eval:速度最快,使⽤eval包裹模块代码,</p>
<p>source-map： 产⽣.map⽂件</p>
<p>cheap:较快，不包含列信息</p>
<p>Module：第三⽅模块，包含loader的sourcemap（⽐如jsx to js ，</p>
<p>babel的sourcemap）</p>
<p>inline： 将.map作为DataURI嵌⼊，不单独⽣成.map⽂件</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">devtool:<span class="string">"cheap-module-eval-source-map"</span>,<span class="comment">// 开发环境配置</span></span><br><span class="line"><span class="comment">//线上不推荐开启</span></span><br><span class="line">devtool:<span class="string">"cheap-module-source-map"</span>, <span class="comment">// 线上⽣成配置</span></span><br></pre></td></tr></table></figure>

<h2 id="WebpackDevServer"><a href="#WebpackDevServer" class="headerlink" title="WebpackDevServer"></a><strong>WebpackDevServer</strong></h2><p><strong>WebpackDevServer</strong>是提升开发效率的利器，每次改完代码都需要重新打包⼀次，打开浏览器，刷新⼀次，很麻烦,我们可以安装使⽤webpackdevserver来改善这块的体验，实现本地开发服务的效果。</p>
<p>启动服务后，会发现dist⽬录没有了，这是因为devServer把打包后的模块不会放在dist⽬录下，⽽是放到内存中，从⽽提升速度。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//在webpack.config.js配置：</span></span><br><span class="line">devServer: &#123;</span><br><span class="line"> contentBase: <span class="string">"./dist"</span>,</span><br><span class="line"> <span class="comment">//是否自动打开浏览器窗口</span></span><br><span class="line"> open: <span class="literal">true</span>,</span><br><span class="line"> port: <span class="number">8080</span></span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure>

<h3 id="本地mock"><a href="#本地mock" class="headerlink" title="本地mock"></a>本地mock</h3><p>前后端分离，前端会依赖后端的接口，一般后端会先给接口文档与接口联调日期。此时，前端就可以本地mock数据，而不打断自己的开发节奏。联调期间，前后端分离，直接获取数据会跨域，上线后我们使⽤nginx转发，开发期间，webpack就可以搞定这件事。</p>
<p>这个时候，我们使用express启动⼀个服务器，并mock⼀个接⼝：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建⼀个server.js 修改scripts "server":"node server.js"</span></span><br><span class="line"><span class="comment">//server.js</span></span><br><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>);</span><br><span class="line"><span class="keyword">const</span> app = express();</span><br><span class="line">app.get(<span class="string">'/api/info'</span>, (req,res)=&gt;&#123;</span><br><span class="line">    res.json(&#123;&#125;)</span><br><span class="line">&#125;);</span><br><span class="line">app.listen(<span class="string">'9092'</span>)</span><br><span class="line"><span class="comment">//node server.js</span></span><br></pre></td></tr></table></figure>

<p>用axios直接去请求端口会发生跨域问题：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//index.js</span></span><br><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">'axios'</span></span><br><span class="line">axios.get(<span class="string">'http://localhost:9092/api/info'</span>).then(<span class="function"><span class="params">res</span>=&gt;</span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(res)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>这个时候，我们可以直接用webpack.config.js来设置服务器代理，从而解决跨域问题，使用代理的方式将本地的请求代理到目标服务器生，从而打破同源策略。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">proxy: &#123;</span><br><span class="line">    <span class="string">"/api"</span>: &#123;</span><br><span class="line">        target: <span class="string">"http://localhost:9092"</span></span><br><span class="line">     &#125;</span><br><span class="line">&#125;,</span><br><span class="line"><span class="comment">//使用钩子函数快速地获取数据</span></span><br><span class="line">before()&#123;</span><br><span class="line">     app.get(<span class="string">"/api/mock.json"</span>, (req, res) =&gt; &#123;</span><br><span class="line">         res.json(&#123;</span><br><span class="line">             hello: <span class="string">"express"</span></span><br><span class="line">         &#125;);</span><br><span class="line">     &#125;);</span><br><span class="line">&#125;,</span><br><span class="line">after()&#123;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="Hot-Module-Replacement热模块替换"><a href="#Hot-Module-Replacement热模块替换" class="headerlink" title="Hot Module Replacement热模块替换"></a><strong>Hot Module Replacement</strong>热模块替换</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">devServer: &#123;</span><br><span class="line">    contentBase: <span class="string">"./dist"</span>,</span><br><span class="line">    open: <span class="literal">true</span>,</span><br><span class="line">    hot:<span class="literal">true</span>,</span><br><span class="line">    <span class="comment">//即便HMR不⽣效，浏览器也不⾃动刷新，就开启hotOnly</span></span><br><span class="line">    hotOnly:<span class="literal">true</span></span><br><span class="line"> &#125;</span><br><span class="line"><span class="comment">//在配置头文件引入webpack，并在插件配置处添加</span></span><br></pre></td></tr></table></figure>

<p>注意启动HMR后，css抽离会不⽣效，因此需要使用css-loader，还有不⽀持contenthash，chunkhash。</p>
<p>contenthash、chunkhash、hash三个占位符的区别：</p>
<p>Hash：构建依赖，每次构建时都会生成一个新的Hash;</p>
<p>chunkHash：当我们有多入口时，我们可以使用chunkHash，别的入口文件发生改变，不会影响其他入口文件；</p>
<p>contentHash：当在JS里面有CSS的话，CSS一般使用contentHsh，因为content跟内容有关系。修改了JS的话，即使没有改变CSS,用hash或者chunkHash均会引起变更，但用contentHash则会进行对比，不会变更。</p>
<h3 id="JS模块的HMR"><a href="#JS模块的HMR" class="headerlink" title="JS模块的HMR"></a>JS模块的HMR</h3><p>需要使⽤module.hot.accept来观察模块更新 从⽽更新，当module.hot开启时，则手动地进行。react hot loader可以实时地调整react组件，同样Vue-loader自身就支持HMR。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> counter <span class="keyword">from</span> <span class="string">"./counter"</span>;</span><br><span class="line"><span class="keyword">import</span> number <span class="keyword">from</span> <span class="string">"./number"</span>;</span><br><span class="line">counter();</span><br><span class="line">number();</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">module</span>.hot) &#123;</span><br><span class="line"> <span class="built_in">module</span>.hot.accept(<span class="string">"./b"</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">     <span class="built_in">document</span>.body.removeChild(<span class="built_in">document</span>.getElementById(<span class="string">"number"</span>));</span><br><span class="line">     number();</span><br><span class="line"> &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="Babel"><a href="#Babel" class="headerlink" title="Babel"></a>Babel</h2><p>Babel是JavaScript编译器，能将ES6代码转换成ES5代码，让我们开发过程中放⼼使⽤JS新特性⽽不⽤担⼼兼容性问题。并且还可以通过插件机制根据需求灵活的扩展。Babel在执⾏编译的过程中，会从项⽬根⽬录下的.babelrc JSON⽂件中读取配置。没有该⽂件会从loader的options地⽅读取配置。</p>
<p>env是babel7之后推⾏的预设插件，会根据各大浏览器支持的程度来进行语法的转换。</p>
<p>babel-loader是webpack 与 babel的通信桥梁，不会做把es6转成es5的⼯作，这部分⼯作需要⽤到@babel/preset-env来做。@babel/preset-env⾥包含了es，6，7，8转es5的转换规则</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line"> test: <span class="regexp">/\.js$/</span>,</span><br><span class="line"> exclude: <span class="regexp">/node_modules/</span>,</span><br><span class="line"> use: &#123;</span><br><span class="line">     loader: <span class="string">"babel-loader"</span>,</span><br><span class="line">     options: &#123;</span><br><span class="line">         <span class="comment">//语法转换插件:preset-env</span></span><br><span class="line">         presets: [<span class="string">"@babel/preset-env"</span>],</span><br><span class="line">     &#125;</span><br><span class="line"> &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="polyfill"><a href="#polyfill" class="headerlink" title="polyfill"></a>polyfill</h3><p>通过上⾯的⼏步 还不够，默认的Babel只⽀持let等⼀些基础的特性转换，Promise等⼀些还有转换过来，这时候需要借助@babel/polyfifill，把es的新特性都装进来，来弥补低版本浏览器中缺失的特性。polyfill垫片 其实是ES6+的ECMA规范库，本身并不进行转换，还是交给@babel/preset-env来进行转换。</p>
<p>如果以全局变量的方式注入，往往会造成全局变量的污染，会发现打包的体积⼤了很多，这是因为polyfifill默认会把所有特性注⼊进来，假如我想我⽤到的es6+，才会注⼊，没⽤到的不注⼊，从⽽减少打包的体积，这就是所谓的按需加载。垫片的含义就是：缺少什么才垫上什么。</p>
<p>polyfill对全局变量有污染问题，但在正常做开发时Promise本来就挂在window上，因此没有区别。但做UI库、组件库、工具库时会有问题。</p>
<p>这个时候我们可以修改Webpack.config.js：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">options: &#123;</span><br><span class="line">    presets: [</span><br><span class="line">        [</span><br><span class="line">            <span class="string">"@babel/preset-env"</span>,</span><br><span class="line">            &#123;</span><br><span class="line">                targets: &#123;</span><br><span class="line">                edge: <span class="string">"17"</span>,</span><br><span class="line">                firefox: <span class="string">"60"</span>,</span><br><span class="line">                chrome: <span class="string">"67"</span>,</span><br><span class="line">                safari: <span class="string">"11.1"</span></span><br><span class="line">                &#125;,</span><br><span class="line">                corejs: <span class="number">2</span>,<span class="comment">//新版本需要指定核⼼库版本</span></span><br><span class="line">                useBuiltIns: <span class="string">"usage"</span><span class="comment">//按需注⼊</span></span><br><span class="line">            &#125;</span><br><span class="line">        ]</span><br><span class="line">    ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>useBuiltIns选项是babel 7的新功能，这个选项告诉babel如何配置@babel/polyfill。 它有三个参数可以使⽤： 1、entry: 需要在webpack的⼊⼝⽂件⾥import “@babel/polyfill”⼀次。babel会根据你的使⽤情况导⼊垫⽚，没有使⽤的功能不会被导⼊相应的垫⽚。</p>
<p>2、usage: 不需要import，全⾃动检测，但是要安装@babel/polyfill。（试验阶段） </p>
<p>3、false: 如果你import”@babel/polyfill”，它不会排除掉没有使⽤的垫⽚，程序体积会庞⼤。(不推荐)</p>
<p>请注意： usage 的⾏为类似 babel-transform-runtime，不会造成全局污染，因此也会不会对类似 Array.prototype.includes() 进⾏polyfill。</p>
<h3 id="babelrc"><a href="#babelrc" class="headerlink" title="babelrc"></a>babelrc</h3><p>为避免Webpack.config.js中配置过多，我们将babel的配置单独提取出来。新建.babelrc⽂件，把options部分移⼊到该⽂件中，就可以了</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line"> presets: [</span><br><span class="line">     [</span><br><span class="line">         <span class="string">"@babel/preset-env"</span>,</span><br><span class="line">         &#123;</span><br><span class="line">             targets: &#123;</span><br><span class="line">             edge: <span class="string">"17"</span>,</span><br><span class="line">             firefox: <span class="string">"60"</span>,</span><br><span class="line">             chrome: <span class="string">"67"</span>,</span><br><span class="line">             safari: <span class="string">"11.1"</span></span><br><span class="line">             &#125;,</span><br><span class="line">             corejs: <span class="number">2</span>, <span class="comment">//新版本需要指定核⼼库版本</span></span><br><span class="line">             useBuiltIns: <span class="string">"usage"</span> <span class="comment">//按需注⼊</span></span><br><span class="line">         &#125;</span><br><span class="line">     ]</span><br><span class="line"> ]</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//webpack.config.js</span></span><br><span class="line">&#123;</span><br><span class="line"> test: <span class="regexp">/\.js$/</span>,</span><br><span class="line"> exclude: <span class="regexp">/node_modules/</span>,</span><br><span class="line"> loader: <span class="string">"babel-loader"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="React"><a href="#React" class="headerlink" title="React"></a>React</h3><p>如何让babel支持JSX的语法，babel的插件其实分为两种，一种为语法转换，另一种为语法的解析。安装babel与react转换的插件： @babel/preset-react。</p>
<p>在babelrc⽂件⾥添加：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="string">"presets"</span>: [</span><br><span class="line">        [</span><br><span class="line">            <span class="string">"@babel/preset-env"</span>,</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="string">"targets"</span>: &#123;</span><br><span class="line">                    <span class="string">"edge"</span>: <span class="string">"17"</span>,</span><br><span class="line">                    <span class="string">"firefox"</span>: <span class="string">"60"</span>,</span><br><span class="line">                    <span class="string">"chrome"</span>: <span class="string">"67"</span>,</span><br><span class="line">                    <span class="string">"safari"</span>: <span class="string">"11.1"</span>,</span><br><span class="line">                    <span class="string">"Android"</span>:<span class="string">"6.0"</span></span><br><span class="line">                &#125;,</span><br><span class="line">                <span class="string">"useBuiltIns"</span>: <span class="string">"usage"</span>, <span class="comment">//按需注⼊</span></span><br><span class="line">            &#125;</span><br><span class="line">        ],</span><br><span class="line">        <span class="string">"@babel/preset-react"</span></span><br><span class="line">    ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="babel-plugin-transform-runtime"><a href="#babel-plugin-transform-runtime" class="headerlink" title="@babel/plugin-transform-runtime"></a><strong>@babel/plugin-transform-runtime</strong></h3><p>当我们开发的是组件库，⼯具库这些场景的时候，polyfill就不适合了，因为polyfifill是注⼊到全局变量，window下的，会污染全局环境，所以推荐闭包⽅式：@babel/plugin-transform-runtime，它不会造成全局污染。会使用替换的方式。例如本来有promise，这时候会创建_promise，用来替换原来语法的promise。</p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/03/17/vue%E6%BA%90%E7%A0%81/vue%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90(%E4%B8%89)/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/03/17/vue%E6%BA%90%E7%A0%81/vue%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90(%E4%B8%89)/" class="post-title-link" itemprop="url">vue源码分析(三)</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
              
    
              <time title="创建时间：2021-03-17 15:18:39 / 修改时间：16:38:08" itemprop="dateCreated datePublished" datetime="2021-03-17T15:18:39+08:00">2021-03-17</time>
            </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/VUE/" itemprop="url" rel="index"><span itemprop="name">VUE</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>承接上一篇的虚拟DOM和diff算法的简单说明：虚拟DOM其实就是JS对象之间的关系，且vue中使用patch算法将虚拟DOM构建成为真实DOM。而patch得核心就是深度优先、同级比较。且在同级比较得过程中，和两遍循环查找不同的暴力逻辑不一样，diff的过程往往主要是比较头尾节点的区别。</p>
<p>diff：3W1H的方式回答下diff算法。</p>
<p>1、What是什么:是一种算法，在虚拟DOM概念出现后，由虚拟DOM比较出DOM的最小操作的算法。</p>
<p>2、Why为什么要用它：轻量且快速、跨平台性、兼容性，主要说一下它的优点与应用与设计的目的。</p>
<p>3、Where哪里使用了它：在vue的源码中，当两个VNode节点进行patch操作时，如果存在新旧虚拟DON树时，就通过diff算法来比较其变化。</p>
<p>4、How怎么执行：总体上来说：深度优先、同级比较。先从顶层节点开始比较，如果其有孩子，则执行递归向下的策略，一直往孩子节点里面走。同时在同级比较的过程中，是取新旧虚拟DOM的头尾节点，然后进行比较，根据比较得出的四种不同情况，会进行不同的处理方法。</p>
<p>new Watcher =&gt; 初始化的patch():批量创建操作 =&gt; render() =&gt; render中的动态值触发：get() =&gt; dep()开始做依赖收集。</p>
<h1 id="模板编译"><a href="#模板编译" class="headerlink" title="模板编译"></a>模板编译</h1><p>模板编译的主要目标是**将模板(template)转换为渲染函数(render)**。Vue 2.0需要用到VNode描述视图以及各种交互，手写显然不切实际，因此用户只需编写类似HTML代码的Vue模板，通过编译器将模板转换为可返回VNode的render函数。</p>
<p>编译的过程分为三步：解析 =&gt; 优化 =&gt; 生成。</p>
<p>当用户设置template选项或者el选项时，则会执行编译，</p>
<h2 id="解析"><a href="#解析" class="headerlink" title="解析"></a>解析</h2><p>解析器将模板解析为抽象语法树，基于AST可以做优化或者代码生成工作。</p>
<p><strong>/src/compiler/parser/index.js</strong></p>
<p>解析器内部分了<strong>HTML解析器、文本解析器和过滤器解析器</strong>，最主要是HTML解析器.</p>
<p>在HTML解析中，遇到&lt;开始标签，就会入栈，执行start，创建一个AST对象，解析关键的指令：v-for，v-if等等。遇到&gt;结束标签就会出栈。但解析过程比较复杂，因此会消耗大量性能。因此通常我们会使用webpack写一些vue-loader，将template均全部先编译成渲染函数，而不会在浏览器的运行时进行编译。且此时编译器也不用打包至页面中，减少打包体积。</p>
<h2 id="优化"><a href="#优化" class="headerlink" title="优化"></a>优化</h2><p><strong>src/compiler/optimizer.js</strong> </p>
<p><strong>optimize</strong>方法标记一下静态节点，即基本不会变化的节点。优点：1、每次重新渲染时，不需要为静态子树创建新节点；2、虚拟DOM中patch时，可以跳过静态子树。</p>
<h2 id="代码生成"><a href="#代码生成" class="headerlink" title="代码生成"></a>代码生成</h2><p><strong>src/compiler/codegen/index.js</strong></p>
<p>generate方法生成渲染函数代码，转换AST为代码字符串</p>
<h1 id="组件化机制"><a href="#组件化机制" class="headerlink" title="组件化机制"></a>组件化机制</h1><h2 id="组件声明（注册）"><a href="#组件声明（注册）" class="headerlink" title="组件声明（注册）"></a>组件声明（注册）</h2><p><strong>Vue.component()</strong></p>
<p><strong>src/core/global-api/assets.js</strong></p>
<p>利用extend方法创建组件的构造函数，之后在组件注册时，注册this.options到全局中，将配置转换为构造函数并添加到components选项，设置组件的key。由于每个组件都是继承于原组件，因此每个组件中都会有components选项。</p>
<h2 id="组件实例创建及挂载"><a href="#组件实例创建及挂载" class="headerlink" title="组件实例创建及挂载"></a><strong>组件实例创建及挂载</strong></h2><h3 id="整体流程"><a href="#整体流程" class="headerlink" title="整体流程"></a><strong>整体流程</strong></h3><p>首先创建的是根组件，首次_render()时，会得到整棵树的VNode结构</p>
<p>整体流程：new Vue() =&gt; $mount() =&gt; vm._render() =&gt; createElement() =&gt; createComponent() =&gt; patch =&gt; createElm =&gt; createComponent()</p>
<h3 id="创建自定义组件"><a href="#创建自定义组件" class="headerlink" title="创建自定义组件"></a><strong>创建自定义组件</strong></h3><p><strong>src\core\vdom\crete-element.js</strong></p>
<p>createElement实际执行VNode创建的函数，核心是根据标签名称tag做相应的操作生成VNode。由于传入tag是非保留标签，因此判定为自定义组件。其实底层的实现，是通过createComponent去创建自定义组件的。</p>
<p><strong>src/core/vdom/create-component.js</strong></p>
<p>创建自定义组件VNode的地方，保存了上一步处理得到的组件构造函数，props，事件等。</p>
<p>同时安装自定义组件管理钩子，比如初始化钩子。由于用户也可能传递钩子，所以会对所有的钩子进行一个整合。</p>
<h3 id="创建自定义组件实例"><a href="#创建自定义组件实例" class="headerlink" title="创建自定义组件实例"></a><strong>创建自定义组件实例</strong></h3><p>根组件执行更新函数时，会递归创建子元素和子组件，入口createElement</p>
<p><strong>core/vdom/patch.js line751</strong></p>
<p>createElement()首次执行_update()时，patch()会通过createEle()创建根元素，子元素创建研究从这里开始。</p>
<p>结论：组件创建顺序自上而下；组件挂载顺序自下而上。</p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/03/17/vue%E6%BA%90%E7%A0%81/vue%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90(%E4%BA%8C)/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/03/17/vue%E6%BA%90%E7%A0%81/vue%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90(%E4%BA%8C)/" class="post-title-link" itemprop="url">vue源码分析(二)</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-03-17 12:18:39" itemprop="dateCreated datePublished" datetime="2021-03-17T12:18:39+08:00">2021-03-17</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-04-02 16:30:01" itemprop="dateModified" datetime="2021-04-02T16:30:01+08:00">2021-04-02</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/VUE/" itemprop="url" rel="index"><span itemprop="name">VUE</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>承接上一篇文章的内容，在总结完vue的初始化、双向绑定的源码之后，这一篇打算写写vue批量异步更新策略与虚拟DOM、diff算法，由于个人水平有限，可能也只能以一个新手的角度进行分析，不会太过于深入。</p>
<h1 id="异步更新队列"><a href="#异步更新队列" class="headerlink" title="异步更新队列"></a>异步更新队列</h1><p>vue高效的秘诀是批量、异步的更新策略；在一次更新周期之内，可能会有很多组件发生变化，因此需要一次将所有变化全部侦听到，每次都将更新的Watcher放在更新队列中，再一次性更新DOM。这个异步更新队列的关键在于事件循环的机制，</p>
<p>宏任务：代表一个个离散的、独立工作单元，浏览器每完成一个宏任务，在下一个宏任务执行开始前会对页面进行重新渲染，会刷新一次。宏任务主要包括：创建主文档对象、解析HTML、各种事件、定时器。如果在一次宏任务执行完之后，事件循环队列中存在微任务，则会一次性将所有的微任务全部执行，将微任务队列清空之后再重新渲染。微任务主要包括：promise回调函数、DOM发生变化。</p>
<p>vue中如何利用事件队列：用微任务来实现刷新队列这件事，在宏任务执行完之后，全部执行微任务队列，这样把所有的watcher都推出队列中加以执行。</p>
<h2 id="vue的具体实现"><a href="#vue的具体实现" class="headerlink" title="vue的具体实现"></a>vue的具体实现</h2><p>异步：只要侦听到数据变化，Vue将会开启一个队列，并缓存在同一事件循环中发生的所有数据变更。</p>
<p>批量：如果一个watcher被多次触发，只会被推入到队列中一次。去重对于避免不必要的计算和DOM操作是非常重要的。然后，在下一个事件循环的tick中，Vue刷新队列并执行实际工作。</p>
<p>异步策略：Vue在内部对异步队列尝试使用原生的Promise.then、MutationObserver和setImmediate，只有在执行环境不支持时，则会采用setTimeOut来代替。</p>
<h2 id="整体流程"><a href="#整体流程" class="headerlink" title="整体流程"></a>整体流程</h2><h3 id="core-observer-watcher-js"><a href="#core-observer-watcher-js" class="headerlink" title="core\observer\watcher.js"></a><strong>core\observer\watcher.js</strong></h3><p>dep.notify()之后watcher执行更新，执行入队操作，在Watcher类中有一个方法upDate()，执行queueWatcher()。</p>
<h3 id="core-observer-scheduler-js"><a href="#core-observer-scheduler-js" class="headerlink" title="core\observer\scheduler.js"></a><strong>core\observer\scheduler.js</strong></h3><p>queueWatcher()执行watcher入队操作，只有在不重复时才会采取入队操作。</p>
<h3 id="core-util-next-tick-js"><a href="#core-util-next-tick-js" class="headerlink" title="core\util\next-tick.js"></a><strong>core\util\next-tick.js</strong></h3><p>nextTick(flushSchedulerQueue) 按照特定异步策略执行队列操作。同时根据当前浏览器支持的类型，来判断通过哪种方式来实现异步更新。watcher中update执行三次，但run仅执行一次</p>
<h1 id="虚拟DOM"><a href="#虚拟DOM" class="headerlink" title="虚拟DOM"></a>虚拟DOM</h1><p>虚拟DOM是对DOM的JS抽象表示，它们是JS对象，能够描述DOM结构和关系，应用的各种状态会作用与虚拟DOM，最终映射到DOM上。</p>
<p>JS操作 = reactive =&gt; 虚拟DOM = patch =&gt; DOM = events =&gt; JS操作。虚拟DOM其实是用JS操作数据时的中间层，reactive即为数据的响应式，patch即为后续的补丁操作与diff算法；其关键作用是让我们直接跟数据去打交道，而不用去操作DOM。</p>
<h2 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h2><p>1、轻量且快速：当它们发生变化时通过新旧虚拟DOM对比可以得到最小DOM操作量，从而提升性能。</p>
<p>2、跨平台性：将虚拟DOM更新转换成不同运行时的特殊操作，从而实现跨平台的效果。</p>
<p>3、兼容性：还可以加入兼容性代码来增强操作的兼容性。</p>
<h2 id="必要性"><a href="#必要性" class="headerlink" title="必要性"></a>必要性</h2><p>在vue1.0中每一个绑定都会有一个Watcher，因此当数据量过大时，则绑定过多、影响性能。这就是所谓的细粒度的数据变化侦测，在vue2.0中选择了中等细粒度的解决方案，每一个组件才有一个watcher实例。虽然不能马上知道数据修改的情况，但这样可以在每次状态变化时通知到组件，再通过引入虚拟DOM去进行对比和渲染。</p>
<h2 id="整体流程-1"><a href="#整体流程-1" class="headerlink" title="整体流程"></a>整体流程</h2><h3 id="core-instance-lifecycle-js"><a href="#core-instance-lifecycle-js" class="headerlink" title="core/instance/lifecycle.js**"></a>core/instance/lifecycle.js**</h3><p>**mountComponent() **</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义更新函数 </span></span><br><span class="line"><span class="keyword">const</span> updateComponent = <span class="function"><span class="params">()</span> =&gt;</span> &#123; </span><br><span class="line">    <span class="comment">// 实际调用是在lifeCycleMixin中定义的_update和renderMixin中定义的_render</span></span><br><span class="line">    vm._update(vm._render(), hydrating) </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>**update()**：负责更新dom，转换vnode为dom。</p>
<h3 id="render-core-instance-render-js"><a href="#render-core-instance-render-js" class="headerlink" title="_render core/instance/render.js"></a><strong>_render core/instance/render.js</strong></h3><p>生成虚拟dom</p>
<h3 id="platforms-web-runtime-index-js"><a href="#platforms-web-runtime-index-js" class="headerlink" title="platforms/web/runtime/index.js"></a><strong>platforms/web/runtime/index.js</strong></h3><p><strong>patch</strong>()函数可根据平台的不同，生成特有的代码。patch是createPatchFunction的返回值，传递nodeOps和modules是web平台特别实现</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> patch: <span class="built_in">Function</span> = createPatchFunction(&#123; nodeOps, modules &#125;)</span><br></pre></td></tr></table></figure>

<p>watcher.run() =&gt; componentUpdate() =&gt; render() =&gt; update() =&gt; patch()</p>
<h2 id="patch实现"><a href="#patch实现" class="headerlink" title="patch实现"></a>patch实现</h2><p>首先进行树级别比较，可能有三种情况：增删改。</p>
<p>1、new VNode不存在就删；</p>
<p>2、old VNode不存在就增；</p>
<p>3、都存在就执行diff执行更新</p>
<p>patch遵循的逻辑是=&gt; 深度优先、同级比较。</p>
<p>即当新老节点都有孩子时，则继续往子节点进行比较，同样这样的比较过程是递归的。</p>
<p>比较两个VNode，包括三种类型操作：<strong>属性更新、文本更新、子节点更新</strong></p>
<p>具体规则如下：</p>
<p>1、新老节点均有children子节点，则对子节点进行diff操作，调用updateChildren()，这里其实就是vue的diff算法。</p>
<p>2、如果老节点没有子节点而新节点有子节点，先清空老节点的文本内容，然后为其新增子节点。</p>
<p>3、当新节点没有子节点而老节点有子节点的时候，则移除该节点的所有子节点。</p>
<p>4、当新老节点都无子节点的时候，只是文本的替换。</p>
<h3 id="updateChildren"><a href="#updateChildren" class="headerlink" title="updateChildren()"></a>updateChildren()</h3><p>updateChildren主要作用是用一种较高效的方式比对新旧两个VNode的children得出最小操作补丁。执行一个双循环是传统方式，vue中针对web场景特点做了特别的算法优化。我们大部分的操作往往是前后节点的改变，很少直接在中间节点处进行大量修改，因此重点关注首尾进行比较。</p>
<p>在新老两组VNode节点的左右头尾两侧都有一个变量标记，在<strong>遍历过程中这几个变量都会向中间靠拢</strong>。 </p>
<p>当<strong>oldStartIdx &gt; oldEndIdx</strong>或者<strong>newStartIdx &gt; newEndIdx</strong>时结束循环。遍历的规则如下：</p>
<p>首先，oldStartVnode、oldEndVnode与newStartVnode、newEndVnode<strong>两两交叉比较</strong>，共有4种比较方法。</p>
<p>1、当 oldStartVnode和newStartVnode 或者 oldEndVnode和newEndVnode 满足sameVnode，直接将该VNode节点进行patchVnode即可，不需再遍历就完成了一次循环。</p>
<p>2、如果oldStartVnode与newEndVnode满足sameVnode。说明oldStartVnode已经跑到了oldEndVnode后面去了，进行patchVnode的同时还需要将真实DOM节点移动到oldEndVnode的后面。</p>
<p>3、如果oldEndVnode与newStartVnode满足sameVnode，说明oldEndVnode跑到了oldStartVnode的前面，进行patchVnode的同时要将oldEndVnode对应DOM移动到oldStartVnode对应DOM的前面。</p>
<p>4、如果以上情况均不符合，则在old VNode中找与newStartVnode满足sameVnode的vnodeToMove，若存在执行patchVnode，同时将vnodeToMove对应DOM移动到oldStartVnode对应的DOM的前面。当然也有可能newStartVnode在old VNode节点中找不到一致的key，或者是即便key相同却不是sameVnode，这个时候会调用createElm创建一个新的DOM节点。</p>
<p>至此循环结束，但是我们还需要处理剩下的节点。</p>
<p>1、当结束时oldStartIdx &gt; oldEndIdx，这个时候旧的VNode节点已经遍历完了，但是新的节点还没有。说明了新的VNode节点实际上比老的VNode节点多，需要将剩下的VNode对应的DOM插入到真实DOM中，此时调用addVnodes（批量调用createElement接口）。</p>
<p>2、但是，当结束时newStartIdx &gt; newEndIdx时，说明新的VNode节点已经遍历完了，但是老的节点还有剩余，需要从文档中删 的节点删除。</p>
<p>上面的遍历查找方法就是diff算法的逻辑。</p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/03/16/vue%E6%BA%90%E7%A0%81/vue%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90(%E4%B8%80)/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/03/16/vue%E6%BA%90%E7%A0%81/vue%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90(%E4%B8%80)/" class="post-title-link" itemprop="url">vue源码分析(一)</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-03-16 12:18:39" itemprop="dateCreated datePublished" datetime="2021-03-16T12:18:39+08:00">2021-03-16</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-05-14 15:35:29" itemprop="dateModified" datetime="2021-05-14T15:35:29+08:00">2021-05-14</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/VUE/" itemprop="url" rel="index"><span itemprop="name">VUE</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>现在自己所用的技术栈基本都是vue2.0，之前有了解过vue的基本原理，即：数据劫持、收集依赖、派发更新。但具体的实现过程并没有去深究，这里想尝试去的去了解下vue2的源码，看看到底是如何实现的。由于自己的水平有限，可能不能深入地进行分析，只能简要说明源码各部分的用法跟自己的理解吧。第一篇中主要想解释下<strong>vue的初始化与其响应式配置</strong>的源码。</p>
<h1 id="vue初始化"><a href="#vue初始化" class="headerlink" title="vue初始化"></a>vue初始化</h1><h3 id="platforms-web-entry-runtime-with-compiler-js"><a href="#platforms-web-entry-runtime-with-compiler-js" class="headerlink" title="platforms/web/entry-runtime-with-compiler.js"></a><strong>platforms/web/entry-runtime-with-compiler.js</strong></h3><p>不管你在初始化vue实例时，使用的是template、el、还是render函数，其判定的优先级是render &gt; template &gt; le。且这段代码会根据你使用的不同方式，采取不同的处理方法，最终都会得到渲染函数，也就是一个render方法，再将render函数放置在opions.render中。<strong>其实实现的就是扩展了$mount方法，能够处理el和template，执行模板解析与编译工作</strong></p>
<h3 id="platforms-web-runtime-index-js"><a href="#platforms-web-runtime-index-js" class="headerlink" title="platforms/web/runtime/index.js"></a><strong>platforms/web/runtime/index.js</strong></h3><p>1、安装web平台特有指令和组件；</p>
<p>2、实现<strong>patch方法</strong>：补丁函数，把用户传入的虚拟DOM转换成为真实DOM，其实只有2种执行情景：初始化时的赋值、每次用户更新DOM时的diff算法。</p>
<p>3、实现**$mount**：一般我们使用$mount就是传入一个宿主文件，将vue挂载在该宿主文件上。该功能就是初始化，然后将首次渲染的结果来替换el。</p>
<h3 id="core-index-js"><a href="#core-index-js" class="headerlink" title="core/index.js"></a><strong>core/index.js</strong></h3><p><strong>初始化全局api</strong></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Vue.set = <span class="keyword">set</span> </span><br><span class="line">Vue.delete = del </span><br><span class="line">Vue.nextTick = nextTick </span><br><span class="line">initUse(Vue) // 实现Vue.use函数 </span><br><span class="line">initMixin(Vue) // 实现Vue.mixin函数 </span><br><span class="line">initExtend(Vue) // 实现Vue.extend函数 </span><br><span class="line">initAssetRegisters(Vue) // 注册实现Vue.component/directive/filter</span><br></pre></td></tr></table></figure>

<h3 id="core-instance-index-js"><a href="#core-instance-index-js" class="headerlink" title="core/instance/index.js"></a><strong>core/instance/index.js</strong></h3><p>该文件定义了vue的构造函数方法，其实只是执行了this._init方法。</p>
<p>同时定义了并混入了很多的实例方法：1、和状态相关的实例方法stateMixin中的$set、$delete、$watch、$data、$props。2、事件相关的方法：$on、$once、$off、$emit。3、生命周期相关的方法。4、渲染函数相关的方法。$nextTick：什么时候用，做了一个操作，同时你想立刻看到数据的结果，则你必须写在$nextTick的回调函数中，因为vue是一个异步更新的框架。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Vue</span> (<span class="params">options</span>) </span>&#123; </span><br><span class="line"><span class="comment">// 构造函数仅执行了_init </span></span><br><span class="line"><span class="keyword">this</span>._init(options) </span><br><span class="line">&#125;</span><br><span class="line">initMixin(Vue) <span class="comment">// 实现init函数,通过该方法给vue添加_init方法。</span></span><br><span class="line">stateMixin(Vue) <span class="comment">// 状态相关api $data,$props,$set,$delete,$watch </span></span><br><span class="line">eventsMixin(Vue)<span class="comment">// 事件相关api $on,$once,$off,$emit </span></span><br><span class="line">lifecycleMixin(Vue) <span class="comment">// 生命周期api _update,$forceUpdate,$destroy </span></span><br><span class="line">renderMixin(Vue)<span class="comment">// 渲染api _render,$nextTick</span></span><br></pre></td></tr></table></figure>

<h3 id="core-instance-init-js"><a href="#core-instance-init-js" class="headerlink" title="core/instance/init.js"></a><strong>core/instance/init.js</strong></h3><p>创建组件实例的init方法的定义，初始化其数据、属性、事件等</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">initLifecycle(vm) </span><br><span class="line"><span class="comment">// $parent,$root,$children,$refs 设置跟生命周期相关的变量，</span></span><br><span class="line"><span class="comment">//先设置父组件、祖先组件，因此组件创建的顺序是自上而下的，但挂载的顺序是自下而上的。</span></span><br><span class="line">initEvents(vm) </span><br><span class="line"><span class="comment">//添加监听父组件传递的事件和回调，因为事件谁派发，谁监听，事件本身就是子组件在派发。</span></span><br><span class="line">initRender(vm)</span><br><span class="line"><span class="comment">//render(h) 此处的$createElement就是h;</span></span><br><span class="line"><span class="comment">//声明$slots,$scopedSlots,_c,$createElement，在此初始化插槽内容。</span></span><br><span class="line">callHook(vm, <span class="string">'beforeCreate'</span>)</span><br><span class="line"><span class="comment">//调用beforeCreate钩子</span></span><br><span class="line">initInjections(vm) <span class="comment">// 获取注入数据,依赖注入</span></span><br><span class="line">initState(vm) </span><br><span class="line"><span class="comment">// 初始化props，methods，data，computed，watch，同时实现响应式。</span></span><br><span class="line">initProvide(vm) <span class="comment">// 提供数据注入，注入的数据是不会做响应式的</span></span><br><span class="line">callHook(vm, <span class="string">'created'</span>)</span><br></pre></td></tr></table></figure>

<h3 id="core-instance-lifecycle-js"><a href="#core-instance-lifecycle-js" class="headerlink" title="core/instance/lifecycle.js**"></a>core/instance/lifecycle.js**</h3><p><strong>mountComponent</strong>：声明了updateComponent，且创建了一个与组件相关的Watcher。</p>
<p><strong>render</strong>：渲染组件，调用用户定义的render方法，获取vdom</p>
<p><strong>updateComponent</strong>：执行更新，将传入的</p>
<p>5-vdom转换为dom，初始化时执行的是dom创建操作，在任何组件发生变化时，updateComponent该函数都会重新再执行一次。</p>
<h3 id="总体流程"><a href="#总体流程" class="headerlink" title="总体流程"></a><strong>总体流程</strong></h3><p>new Vue()：调用init =&gt; _init()：初始化各种属性 =&gt; $mount()：调用mountComponent =&gt; mountComponent：声明了一个updateComponent函数，并创建一个和组件相关的Watcher =&gt; render()：获取虚拟DOM =&gt; update(Component)：把虚拟DOM转换为真实DOM</p>
<h1 id="数据响应式"><a href="#数据响应式" class="headerlink" title="数据响应式"></a>数据响应式</h1><h3 id="src-core-instance-state-js"><a href="#src-core-instance-state-js" class="headerlink" title="src\core\instance\state.js"></a><strong>src\core\instance\state.js</strong></h3><p>初始化数据，包括props、methods、data、computed和watch。</p>
<p>核心代码<strong>initData</strong>将data数据响应化，即代理这些数据到实例上，同时会判断是否有重复。</p>
<h3 id="core-observer-index-js"><a href="#core-observer-index-js" class="headerlink" title="core/observer/index.js"></a><strong>core/observer/index.js</strong></h3><p>observe方法返回一个Observer实例。如果已经存在则直接返回，不存在则新建实例对象。每一个响应式的数据对象，都会被附加上一个Observer来进行监听。</p>
<p>Observer对象根据数据类型执行对应的响应化操作，在保存值得同时，还会额外生成一个dep。DEP的用法：1、我们操作数组通常使用push、pop、splice等方法，此时是没有办法得知数组的变化的，这时候vue采取的策略是拦截这些方法，并通知对应dep。2、object里面新增或者删除属性，同样也会通知dep。</p>
<p>defineReactive定义对象属性的getter/setter，getter负责添加依赖，setter负责通知更新。如果有依赖存在，则收集依赖，同时会查询是否存在子ob，子ob也会收集这个依赖，同时如果值是数组，同样要将其所有项均添加依赖。有几个key就会有几个dep，Watcher一个组件只会有1个，Observer一个对象就会有一个。</p>
<h3 id="core-observer-dep-js"><a href="#core-observer-dep-js" class="headerlink" title="core/observer/dep.js"></a><strong>core/observer/dep.js</strong></h3><p>Dep负责管理一组Watcher，包括watcher实例的增删及通知更新</p>
<h3 id="Watcher"><a href="#Watcher" class="headerlink" title="Watcher"></a><strong>Watcher</strong></h3><p>Watcher解析一个表达式并收集依赖，当数值变化时触发回调函数，常用于$watch API和指令中。</p>
<p>有对应的Watcher，数值变化会触发其update函数导致重新渲染。相关API： $watcher</p>
<h3 id="src-core-observer-array-js"><a href="#src-core-observer-array-js" class="headerlink" title="src\core\observer\array.js"></a><strong>src\core\observer\array.js</strong></h3><p>数组数据变化的侦测跟对象不同，我们操作数组通常使用push、pop、splice等方法，此时没有办法得知数据变化。所以vue中采取的策略是拦截这些方法并通知dep。为数组原型中的7个可以改变内容的方法定义拦截器<strong>Observer****中覆盖数组原型</strong></p>
<h3 id="vue2-0响应式的缺点："><a href="#vue2-0响应式的缺点：" class="headerlink" title="vue2.0响应式的缺点："></a>vue2.0响应式的缺点：</h3><p>1、递归、循环遍历比较多，性能会收到影响，因此vue3.0使用proxy来代替，只用在外面加上一层代理。</p>
<p>2、API不统一，对于数组与对象采取的是两套方法来进行实现。</p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/03/12/%E5%AD%A6%E6%A0%A1%E9%A1%B9%E7%9B%AE%E5%BA%94%E7%94%A8/%E9%A6%96%E5%B1%8F%E4%BC%98%E5%8C%96%E5%AE%9E%E8%B7%B5/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/03/12/%E5%AD%A6%E6%A0%A1%E9%A1%B9%E7%9B%AE%E5%BA%94%E7%94%A8/%E9%A6%96%E5%B1%8F%E4%BC%98%E5%8C%96%E5%AE%9E%E8%B7%B5/" class="post-title-link" itemprop="url">首屏优化实践</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-03-12 20:02:33" itemprop="dateCreated datePublished" datetime="2021-03-12T20:02:33+08:00">2021-03-12</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:32:35" itemprop="dateModified" datetime="2021-11-03T16:32:35+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E5%AD%A6%E6%A0%A1%E9%A1%B9%E7%9B%AE%E5%BA%94%E7%94%A8/" itemprop="url" rel="index"><span itemprop="name">学校项目应用</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>之前的项目在部署之后首屏加载过慢了，因此最近有想进行简单的优化一下，顺便就想以一个初学者的角度将项目优化的思路有条理的梳理一下，因为水平原因，很多方法可能只能写下思路，没办法应用在自己的项目上，而且可能很多的优化方案已经略有过时。主要还是想做一下有关优化知识的梳理吧，毕竟优化是一个永恒不变的话题。项目是基于vue框架开发的，但优化方法的思路是不拘泥于框架的。</p>
<h1 id="代码层面优化"><a href="#代码层面优化" class="headerlink" title="代码层面优化"></a>代码层面优化</h1><p>代码层面的优化这一部分其实比较杂乱，浅显的意思是要怎么去编写高性能点的代码？emm主要还是讲一下从一个新手的角度，避免出现一些影响性能的操作吧。</p>
<h2 id="路由、模块懒加载"><a href="#路由、模块懒加载" class="headerlink" title="路由、模块懒加载"></a>路由、模块懒加载</h2><p>很常用的懒加载代码， 不用一次加载所有的路由或者模块，到需要引用时再进行加载，用函数来代替对象进行引入模块与路由，属于用vue框架时的基本操作吧。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//路由懒加载</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">new</span> Router(&#123;</span><br><span class="line">  routes: [</span><br><span class="line">    &#123;</span><br><span class="line">      path: <span class="string">'/'</span>,</span><br><span class="line">      name: <span class="string">'HelloWorld'</span>,</span><br><span class="line">      <span class="comment">// 方法一：vue异步组件实现</span></span><br><span class="line">      <span class="comment">// component: resolve =&gt; (require(['@/components/HelloWorld'], resolve))</span></span><br><span class="line">      <span class="comment">// 方法二：import方法(常用)</span></span><br><span class="line">      component: <span class="function"><span class="params">()</span> =&gt;</span> <span class="keyword">import</span>(<span class="string">'@/components/HelloWorld'</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">//模块懒加载</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  components: &#123;</span><br><span class="line">  	<span class="comment">// 方法一</span></span><br><span class="line">    <span class="string">'HelloWorld'</span>: <span class="function"><span class="params">()</span> =&gt;</span> <span class="keyword">import</span>(<span class="string">'./HelloWorld'</span>),</span><br><span class="line">    <span class="comment">// 方法二</span></span><br><span class="line">    <span class="comment">// HelloWorld': resolve =&gt; (['./HelloWorld'], resolve)</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="图片懒加载"><a href="#图片懒加载" class="headerlink" title="图片懒加载"></a>图片懒加载</h2><p>图片懒加载其实通常更多地应用于图片较多的网站，我自己的项目上由于图片比较少，就没有用到复杂的长屏懒加载。只是在图片或模块的ajax请求没有返回时，使用一个loading的特效来代替图片、组件进行填充，简单地实现了基础的懒加载？思路很简单：大概就是预设一个div的z-index，让其覆盖图片、组件的上面，默认为show。同时在ajax请求的异步回调上修改其CSS，变为hidden。elementUI等开源组件库上应该有类似的loading组件。</p>
<p>对于含有多图片的长页面，在你没有滚动到图片所在位置的页面中时，是用空的div来填充代替图片位置的。一旦我们通过滚动使得这个 div 出现在了可见范围内，那么 div 元素的内容就会发生变化，呈现其中的内容，这就是图片的懒加载。</p>
<p>下面我们简单实现下懒加载：其实该功能的关键在于获取两个值：1、当前可视区域的高度，通常用window.innerHeight 属性获取。</p>
<p>2、元素距离可视区域顶部的高度，我们这里选用 getBoundingClientRect() 方法来获取返回元素的大小及其相对于视口的位置。该方法的返回值是一个DOMRect。DOMRect 对象包含了一组用于描述边框的只读属性——left、top、right 和 bottom，单位为像素。除了 width 和 height 外的属性都是相对于视口的左上角位置而言的。其中top 属性代表了元素距离可视区域顶部的高度，正好用来实现功能。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"en"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"viewport"</span> <span class="attr">content</span>=<span class="string">"width=device-width, initial-scale=1.0"</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">"X-UA-Compatible"</span> <span class="attr">content</span>=<span class="string">"ie=edge"</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">title</span>&gt;</span>Lazy-Load<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="css">    <span class="selector-class">.img</span> &#123;</span></span><br><span class="line">      width: 200px;</span><br><span class="line"><span class="css">      <span class="selector-tag">height</span><span class="selector-pseudo">:200px</span>;</span></span><br><span class="line">      background-color: gray;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="css">    <span class="selector-class">.pic</span> &#123;</span></span><br><span class="line">      // 必要的img样式</span><br><span class="line">    &#125;</span><br><span class="line">  <span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"container"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"img"</span>&gt;</span></span><br><span class="line">      // 注意我们并没有为它引入真实的src</span><br><span class="line">      <span class="tag">&lt;<span class="name">img</span> <span class="attr">class</span>=<span class="string">"pic"</span> <span class="attr">alt</span>=<span class="string">"加载中"</span> <span class="attr">data-src</span>=<span class="string">"./images/1.png"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"img"</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">img</span> <span class="attr">class</span>=<span class="string">"pic"</span> <span class="attr">alt</span>=<span class="string">"加载中"</span> <span class="attr">data-src</span>=<span class="string">"./images/2.png"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"img"</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">img</span> <span class="attr">class</span>=<span class="string">"pic"</span> <span class="attr">alt</span>=<span class="string">"加载中"</span> <span class="attr">data-src</span>=<span class="string">"./images/3.png"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"img"</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">img</span> <span class="attr">class</span>=<span class="string">"pic"</span> <span class="attr">alt</span>=<span class="string">"加载中"</span> <span class="attr">data-src</span>=<span class="string">"./images/4.png"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"img"</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">img</span> <span class="attr">class</span>=<span class="string">"pic"</span> <span class="attr">alt</span>=<span class="string">"加载中"</span> <span class="attr">data-src</span>=<span class="string">"./images/5.png"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"img"</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">img</span> <span class="attr">class</span>=<span class="string">"pic"</span> <span class="attr">alt</span>=<span class="string">"加载中"</span> <span class="attr">data-src</span>=<span class="string">"./images/6.png"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"img"</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">img</span> <span class="attr">class</span>=<span class="string">"pic"</span> <span class="attr">alt</span>=<span class="string">"加载中"</span> <span class="attr">data-src</span>=<span class="string">"./images/7.png"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"img"</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">img</span> <span class="attr">class</span>=<span class="string">"pic"</span> <span class="attr">alt</span>=<span class="string">"加载中"</span> <span class="attr">data-src</span>=<span class="string">"./images/8.png"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"img"</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">img</span> <span class="attr">class</span>=<span class="string">"pic"</span> <span class="attr">alt</span>=<span class="string">"加载中"</span> <span class="attr">data-src</span>=<span class="string">"./images/9.png"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"img"</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">img</span> <span class="attr">class</span>=<span class="string">"pic"</span> <span class="attr">alt</span>=<span class="string">"加载中"</span> <span class="attr">data-src</span>=<span class="string">"./images/10.png"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="actionscript">    <span class="comment">// 获取所有的图片标签</span></span></span><br><span class="line"><span class="javascript">    <span class="keyword">const</span> imgs = <span class="built_in">document</span>.getElementsByTagName(<span class="string">'img'</span>)</span></span><br><span class="line"><span class="actionscript">    <span class="comment">// 获取可视区域的高度</span></span></span><br><span class="line"><span class="javascript">    <span class="keyword">const</span> viewHeight = <span class="built_in">window</span>.innerHeight || <span class="built_in">document</span>.documentElement.clientHeight</span></span><br><span class="line"><span class="actionscript">    <span class="comment">// num用于统计当前显示到了哪一张图片，避免每次都从第一张图片开始检查是否露出</span></span></span><br><span class="line"><span class="javascript">    <span class="keyword">let</span> num = <span class="number">0</span></span></span><br><span class="line"><span class="actionscript">    <span class="function"><span class="keyword">function</span> <span class="title">lazyload</span><span class="params">()</span></span>&#123;</span></span><br><span class="line"><span class="javascript">        <span class="keyword">for</span>(<span class="keyword">let</span> i=num; i&lt;imgs.length; i++) &#123;</span></span><br><span class="line"><span class="actionscript">            <span class="comment">// 用可视区域高度减去元素顶部距离可视区域顶部的高度</span></span></span><br><span class="line"><span class="javascript">            <span class="keyword">let</span> distance = viewHeight - imgs[i].getBoundingClientRect().top</span></span><br><span class="line"><span class="actionscript">            <span class="comment">// 如果可视区域高度大于等于元素顶部距离可视区域顶部的高度，说明元素露出</span></span></span><br><span class="line">            if(distance &gt;= 0 )&#123;</span><br><span class="line"><span class="actionscript">                <span class="comment">// 给元素写入真实的src，展示图片</span></span></span><br><span class="line"><span class="actionscript">                imgs[i].src = imgs[i].getAttribute(<span class="string">'data-src'</span>)</span></span><br><span class="line"><span class="actionscript">                <span class="comment">// 前i张图片已经加载完毕，下次从第i+1张开始检查是否露出</span></span></span><br><span class="line">                num = i + 1</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="actionscript">    <span class="comment">// 监听Scroll事件</span></span></span><br><span class="line"><span class="javascript">    <span class="built_in">window</span>.addEventListener(<span class="string">'scroll'</span>, lazyload, <span class="literal">false</span>);</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure>

<h2 id="ajax请求"><a href="#ajax请求" class="headerlink" title="ajax请求"></a>ajax请求</h2><p>现在项目中通常都不会去手写原生的ajax，毕竟因为异步的回调地狱嘛。我自己的项目用的是axios，定义如下：<code>axios</code> 是一个轻量的 <code>HTTP客户端</code>，它基于 <code>XMLHttpRequest</code> 服务来执行 HTTP 请求，支持丰富的配置，支持 <code>Promise</code>，支持浏览器端和 <code>Node.js</code> 端。但往往我们需要封装一下axios，毕竟如果每发起一次HTTP请求，就要把这些比如设置超时时间、设置请求头、根据项目环境判断使用哪个请求地址、错误处理等等操作都重写一遍就太麻烦了。这里贴一下我自己很简单的axios封装。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">'axios'</span></span><br><span class="line"><span class="keyword">var</span> service = axios.create(&#123;</span><br><span class="line">    baseURL: <span class="string">''</span>,</span><br><span class="line">    <span class="comment">// http://qinghai.free.idcfengye.com/</span></span><br><span class="line">    timeout:<span class="number">400000</span>,</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// 添加请求拦截器</span></span><br><span class="line">service.interceptors.request.use(<span class="function"><span class="keyword">function</span> (<span class="params">config</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 在发送请求之前做些什么</span></span><br><span class="line">    <span class="keyword">return</span> config;</span><br><span class="line">&#125;, <span class="function"><span class="keyword">function</span> (<span class="params">error</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 对请求错误做些什么</span></span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">Promise</span>.reject(error);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// 添加响应拦截器</span></span><br><span class="line">service.interceptors.response.use(<span class="function"><span class="keyword">function</span> (<span class="params">response</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 对响应数据做点什么</span></span><br><span class="line">    <span class="keyword">return</span> response;</span><br><span class="line">&#125;, <span class="function"><span class="keyword">function</span> (<span class="params">error</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 对响应错误做点什么</span></span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">Promise</span>.reject(error);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">Vue.prototype.$http = service;</span><br><span class="line"><span class="comment">//挂载在vue的原型上，这样你后续在vue文件中使用this.$http便可以获取到service。</span></span><br></pre></td></tr></table></figure>

<p>当然，这里ajax的优化其实并不是指简单的axios封装，毕竟这个属于常用操作。这个优化问题也是出自项目的主页，由于某些问题，主页中同一时间进行地ajax请求过多，一次跑过多的异步任务会导致页面的卡顿。开始时，我用的便是上述封装后的axios请求，为解决卡顿问题，开始时我希望能够使用fetch()来代替ajax请求，希望能达到目的；fetch()定义如下：</p>
<p>Fetch API是新的ajax解决方案 Fetch会返回Promise, fetch不是ajax的进一步封装，而是原生js，没有使用XMLHttpRequest对象。fetch(url, options).then()。</p>
<p>其实我感觉优点就三个：1、使用promise，这样也支持了async，编写异步时更加方便；2、可自定义是否携带cookie；3、fetch在ServiceWorker中使用。但实际项目中，ajax往往都被封装好了，例如上面的axios，这样前两项其实并没有所谓。但关键就在于第三项了。service work是基于web worker而来。</p>
<p>众所周知，javaScript 是单线程的，随着web业务的复杂化，开发者逐渐在js中做了许多耗费资源的运算过程，这使得单线程的弊端更加凹显。web worker正是基于此被创造出来，它是脱离在主线程之外的，我们可以将复杂耗费时间的事情交给web worker来做。但是web worker作为一个独立的线程，他的功能应当不仅于此。service work便是在web worker的基础上增加了离线缓存的能力。</p>
<p>特点：1、必须是https环境，本地调试localhost或者127.0.0.1环境也是可以的，2、依赖于cache api进行实现的3、依赖于h5的fetch Api；4、依赖于promise进行实现。但这里我自己并没有用这么复杂的优化方案，就不赘述了。</p>
<p>我自己运用基本的处理有：1、使用了axios对多并发请求的处理方案，当页面某个数据来源于多个互不关联的请求时，需要统一处理然后呈现。即使用axios.all(iterable)，参数：请求数组；axios.spread(callback)，参数： 对应请求返回值。API的应用实例如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">methods: &#123;</span><br><span class="line">  getAllTask() &#123;</span><br><span class="line">    <span class="keyword">return</span> axios.get(<span class="string">'/data.json'</span>, &#123;</span><br><span class="line">      params: &#123;</span><br><span class="line">        id: <span class="number">10</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;,</span><br><span class="line">  getAllCity() &#123;</span><br><span class="line">    <span class="keyword">return</span> axios.get(<span class="string">'/city.json'</span>, &#123;</span><br><span class="line">      params: &#123;</span><br><span class="line">        id: <span class="number">11</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;,</span><br><span class="line">mounted() &#123;</span><br><span class="line">  axios.all([<span class="keyword">this</span>.getAllTask(), <span class="keyword">this</span>.getAllCity()])</span><br><span class="line">    .then(axios.spread(<span class="function"><span class="keyword">function</span>(<span class="params">allTask, allCity</span>) </span>&#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">'请求1结果'</span>, allTask)</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">'请求2结果'</span>, allCity)</span><br><span class="line">    &#125;))</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure>

<p>2、尽量复用ajax请求，当不同模块间可以公用同一接口的同一信息时，不要在两个模块中分别请求两次，而是尽量利用组件间通信来实现信息的共享；</p>
<p>2、设置HTTP缓存。HTTP 缓存是我们日常开发中最为熟悉的一种缓存机制。它又分为强缓存和协商缓存。</p>
<h3 id="强缓存"><a href="#强缓存" class="headerlink" title="强缓存"></a>强缓存</h3><p>优先级较高的是强缓存，在命中强缓存失败的情况下，才会走协商缓存。强缓存是利用 http 头中的 Expires 和 Cache-Control 两个字段来控制的。强缓存中，当请求再次发出时，浏览器会根据其中的 expires 和 cache-control 判断目标资源是否“命中”强缓存，若命中则直接从缓存中获取资源，不会再与服务端发生通信。</p>
<p>当服务器返回响应时，在 Response Headers 中将过期时间写入 expires 字段。接下来如果我们试图再次向服务器请求资源，浏览器就会先对比本地时间和 expires 的时间戳，如果本地时间小于 expires 设定的过期时间，那么就直接去缓存中取这个资源。expires写的是一个绝对的时间戳，例如：xxx年x月x日。而在 Cache-Control 中，我们通过 <code>max-age</code>字段 来控制资源的有效期。max-age 不是一个时间戳，而是一个时间长度。max-age 是一个相对时间，这就意味着它有能力规避掉 expires 可能会带来的时差问题。同样，因此cache-control的优先级比expires更高。Cache-Control 中还有更高优先级的s-maxage：用于表示 cache 服务器上（比如 cache CDN）的缓存的有效时间的，并只对 public 缓存有效。（public 与 private 是针对资源是否能够被代理服务缓存而存在的一组对立概念。）</p>
<h3 id="协商缓存"><a href="#协商缓存" class="headerlink" title="协商缓存"></a>协商缓存</h3><p>协商缓存依赖于服务端与浏览器之间的通信。在协商缓存机制下，浏览器需要向服务器去询问缓存的相关信息，进而判断是重新发起请求、下载完整的响应，还是从本地获取缓存的资源。在该服务端提示缓存资源未改动（Not Modified），资源会被重定向到浏览器缓存，这种情况下网络请求对应的状态码是 304）。</p>
<p>实现：<strong>Last-Modified 到 Etag</strong>。Last-Modified 是一个时间戳，如果我们启用了协商缓存，它会在首次请求时随着 Response Headers 返回。随后我们每次请求时，会带上一个叫 If-Modified-Since 的时间戳字段，它的值正是上一次 response 返回给它的 last-modified 值。服务器接收到这个时间戳后，会比对该时间戳和资源在服务器上的最后修改时间是否一致，从而判断资源是否发生了变化。如果发生了变化，就会返回一个完整的响应内容，并在 Response Headers 中添加新的 Last-Modified 值；否则，返回如上图的 304 响应，Response Headers 不会再添加 Last-Modified 字段。</p>
<p>但是可能会有一个bug：我们编辑文件，但没有修改，服务器可能会以为我们修改了；修改文件的时间过快，服务器可能会感知不到。即：服务器并没有正确感知文件的变化。这样就引出了Etag，Etag 是由服务器为每个资源生成的唯一的<strong>标识字符串</strong>，这个标识字符串是基于文件内容编码的，只要文件内容不同，它们对应的 Etag 就是不同的。Etag 的生成过程需要服务器额外付出开销，会影响服务端的性能，这是它的弊端。同样，优先级方面，Etag高于Last-Modefied。</p>
<p>HTTP缓存决策流程：当我们的资源内容不可复用时，直接为 Cache-Control 设置 no-store，拒绝一切形式的缓存；否则考虑是否每次都需要向服务器进行缓存有效确认，如果需要，那么设 Cache-Control 的值为 no-cache；否则考虑该资源是否可以被代理服务器缓存，根据其结果决定是设置为 private 还是 public；然后考虑该资源的过期时间，设置对应的 max-age 和 s-maxage 值；最后，配置协商缓存需要用到的 Etag、Last-Modified 等参数。</p>
<h2 id="组件库按需引入"><a href="#组件库按需引入" class="headerlink" title="组件库按需引入"></a>组件库按需引入</h2><p>这一点其实好理解，例如当你使用elementUI或者echarts这些组件库时，通常并没有用到其提供的全部组件，因此在import的时候，不需要全部引入整体，只需要引入你所用到的部分即可。</p>
<h2 id="适用于V8引擎的JS代码"><a href="#适用于V8引擎的JS代码" class="headerlink" title="适用于V8引擎的JS代码"></a>适用于V8引擎的JS代码</h2><p>毫无疑问，就又是一个大坑了，关于对这个的理解我也是由他人的博客所看来的，不保证结论的正确性，只是记录下自己的了解。首先我们需要了解以下V8引擎底层的两个特征。</p>
<h3 id="隐藏类"><a href="#隐藏类" class="headerlink" title="隐藏类"></a>隐藏类</h3><p>在V8引擎中采用了和动态查找完全不同的技术来实现属性的访问：动态地为对象创建隐藏类。每当一个新的属性被添加到对象中时，对象所对应的隐藏类会随之改变。乍一看似乎每次添加一个属性都创建一个新的隐藏类非常低效。实际上，利用类转移信息时，隐藏类可以被重用。即下次创建一个 Point 对象的时候，就可以直接共享由最初那个 Point 对象所创建出来的隐藏类。</p>
<p>这样的话，相当于一个构造函数中的所有属性都由一个隐藏类的链将他串联在了一起，由该构造函数新建的对象就可以直接共享该隐藏类链。主要的优点有：1、属性访问时不再需要从动态字典中进行查找了；2、为V8使用经典的基于类的优化和内联缓存技术提供了条件。</p>
<p>内联缓存技术：在第一次执行到访问某个对象的属性的代码时，V8会找出该对象的隐藏类；同时，V8会假设在相同的代码片段中其他所有的对象的属性访问都通过这一隐藏类来实现。只有在预测失败时，V8才会修改内联代码并移除刚才加入的内联优化。当有许多对象共享同一个隐藏类的时候，这样的实现方式下属性的访问速度可以接近大多数动态语言。使用内联缓存代码和隐藏类实现属性访问的方式和动态代码生成和优化的方式结合起来，即：你基于一个构造函数，构建多个实例时，用隐藏类的方法可以加快属性访问速度。</p>
<p>由隐藏得来的V8代码编写教训：1、在构造函数里初始化所有对象的成员(所以这些实例之后不会改变其隐藏类)；2、总是以相同的次序初始化对象成员；//可以更好利用隐藏类 3、永远不要delete对象的某个属性；4、方法：重复执行相同方法的代码将比仅执行一次的多个不同方法（由于内联缓存）的代码运行得更快。5、数组：避免稀疏数组</p>
<h3 id="两次编译"><a href="#两次编译" class="headerlink" title="两次编译"></a>两次编译</h3><p>V8有两个不同的运行时编译器：1、“完全”编译器（unoptimized）。一开始，所有的V8代码都运行在unoptimized状态。它的好处是编译速度非常快，它使代码初次执行速度非常快。2、“优化”编译器（optimized）。当V8发现某段代码执行非常热时，它会根据通常的执行路径进行代码优化，生成optimized代码。优化代码的执行速度非常快。</p>
<p>编译器有可能从“优化”状态退回到“完全”状态， 这就是deoptimized。这是很不幸的过程，优化后的代码没法正确执行，不得不退回到unoptimized版本。当然最不幸的是代码不停地被optimized，然后又被deoptimized， 这会带来很大的性能损耗，例如：for…in遍历对象的属性和try…catch中的代码会让编译器无法到达optimized状态。</p>
<p>使用教训：1、把for…in 内部的代码单独提出来作为函数，这样V8引擎就能对其进行优化；2、谨慎使用try..catch</p>
<h3 id="闭包"><a href="#闭包" class="headerlink" title="闭包"></a>闭包</h3><p>闭包会使程序逻辑变复杂，有时会看不清楚是否对象内存被释放，因此要注意释放闭包中的大对象， 否则会引起内存泄露。谨慎使用闭包，有时候不当的闭包使用会造成大量的内存占用。</p>
<h2 id="Tree-shaking和代码分割"><a href="#Tree-shaking和代码分割" class="headerlink" title="Tree-shaking和代码分割"></a>Tree-shaking和代码分割</h2><p>在用cli构建的项目中，可修改配置实现打包时进行tree-shaking的优化，在打包时将没有用到的代码删除掉。</p>
<p>而对于单页应用，如果所有的资源都打包到一个JS中，则体积会十分庞大，需要进行代码分割。</p>
<p>1、分离第三方库代码：修改webpack配置文件，将第三方库的代码单独提取出来，和业务代码进行分离；</p>
<p>2、动态导入：其实就是组件、路由模块的懒加载，在使用import的时候引入指针函数而不是直接引入类或者Component，这样只有在使用的时候才回去加载函数。</p>
<p>3、提取复用的业务代码：除了第三方库代码之外，业务代码也会有复用的情况，比如一些工具的函数库。没必要在不同的模块中重复地进行加载，因此也可以提取出来，进行单独打包。</p>
<h1 id="存储层面的优化"><a href="#存储层面的优化" class="headerlink" title="存储层面的优化"></a>存储层面的优化</h1><p>其实关于缓存方面上面的ajax请求里已经写了好多了，嗯，感觉布局有点问题，不过并不打算改了。这里就主要说说webpack打包方面的修改吧，毕竟算存储内容的优化？不过我是用vuecli构建的项目，其实该有的优化都已经默认配好了？就像tree-shaking？</p>
<p>在你使用vue-cli构建项目时，webpack的配置会被隐藏在vuecli的框架下面，不过想要自己进行特别的webpack配置也比较容易，根据vuecli官方网站的说明：调整 webpack 配置最简单的方式就是在 <code>vue.config.js</code> 中的 <code>configureWebpack</code> 选项提供一个对象：该对象将会被 <a href="https://github.com/survivejs/webpack-merge" target="_blank" rel="noopener">webpack-merge</a> 合并入最终的 webpack 配置。如果你需要基于环境有条件地配置行为，或者想要直接修改配置，那就换成一个函数 (该函数会在环境变量被设置之后懒执行)。该方法的第一个参数会收到已经解析好的配置。在函数内，你可以直接修改配置，或者返回一个将会被合并的对象。具体的配置方案因为我自己也不太懂，就不赘述了。</p>
<h2 id="webpack-bundle-analyzer"><a href="#webpack-bundle-analyzer" class="headerlink" title="webpack-bundle-analyzer"></a>webpack-bundle-analyzer</h2><p>如果你是使用vue-cli3构建的项目，则直接vue-cli-service build –report就会生成一个report.html，打开这个html就能看到webpack打包之后的模块与依赖的加载状态。如果不是由vuecli构建的项目，也很简单，直接npm install 安装webpack-bundle-analyzer模块，版本号过高的话可能有意外的错误，推荐安装5.0.0。之后在vue配置中引入该包，并自定义运行命令即可，具体的可参照官网。之后你就可以看到自己项目的打包分析了，针对那些用的比较少的模块，把全局引入修改成针对性引入、使用更轻量级的组件库。总之根据该打包分析图，尽量减少项目的体积即可。</p>
<h2 id="gzip压缩"><a href="#gzip压缩" class="headerlink" title="gzip压缩"></a>gzip压缩</h2><p>gzip压缩可以说是为了优化首屏加载速度最常用的方法之一了。Gzip 压缩背后的原理，是在一个文本文件中找出一些重复出现的字符串、临时替换它们，从而使整个文件变小。根据这个原理，文件中代码的重复率越高，那么压缩的效率就越高，使用 Gzip 的收益也就越大。反之亦然。主要的实现方法有两个：</p>
<p>1、项目正常打包部署，直接在服务端对nginx配置进行修改。这样设置时，当你请求时,服务端就会先将对应的文件压缩成.gz格式再发送给你，客户端接收到了.gz文件的格式之后再解压并执行后续操作。相当于用压缩的时间，换取了文件传输的时间，通常都会是正优化，除非项目体积过小。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">http &#123;</span><br><span class="line">    include       mime.types;</span><br><span class="line">    default_type  application&#x2F;octet-stream;</span><br><span class="line"></span><br><span class="line">    sendfile        on;</span><br><span class="line">    #tcp_nopush     on;</span><br><span class="line"></span><br><span class="line">    #keepalive_timeout  0;</span><br><span class="line">    keepalive_timeout  65;</span><br><span class="line"></span><br><span class="line">    # 开启gzip</span><br><span class="line">    gzip  on;</span><br><span class="line">    </span><br><span class="line">    # 设置缓冲区大小</span><br><span class="line">    gzip_buffers 4 16k;</span><br><span class="line">    </span><br><span class="line">    #压缩级别官网建议是6</span><br><span class="line">    gzip_comp_level 6;</span><br><span class="line"> </span><br><span class="line">    #压缩的类型</span><br><span class="line">    gzip_types text&#x2F;plain application&#x2F;javascript text&#x2F;css application&#x2F;xml text&#x2F;javascript application&#x2F;x-httpd-php;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    server &#123;</span><br><span class="line">        listen       8462;</span><br><span class="line">        server_name  localhost;</span><br><span class="line"></span><br><span class="line">        location &#x2F; &#123;</span><br><span class="line">            root   dist;</span><br><span class="line">            index  index.html index.htm;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        error_page   500 502 503 504  &#x2F;50x.html;</span><br><span class="line">        location &#x3D; &#x2F;50x.html &#123;</span><br><span class="line">            root   html;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>2、项目打包时对webpack进行特殊设置，安装插件（compression-webpack-plugin）；打包同时生成成两份文件，第一份为正常的文件，另一个为gz压缩后的文件，部署时将其全部部署至服务端。下面是vuecli构建项目的webpack配置参考，不用vuecli构建的，直接修改webpack配置即可。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> CompressionPlugin = <span class="built_in">require</span>(<span class="string">'compression-webpack-plugin'</span>);</span><br><span class="line"><span class="built_in">module</span>.exports= &#123;</span><br><span class="line">    configureWebpack: &#123;</span><br><span class="line">        plugins: [</span><br><span class="line">            <span class="keyword">new</span> CompressionPlugin(&#123;</span><br><span class="line">                algorithm: <span class="string">'gzip'</span>, <span class="comment">// 使用gzip压缩</span></span><br><span class="line">                test: <span class="regexp">/\.js$|\.html$|\.css$/</span>, <span class="comment">// 匹配文件名</span></span><br><span class="line">                filename: <span class="string">'[path].gz[query]'</span>, <span class="comment">// 压缩后的文件名(保持原文件名，后缀加.gz)</span></span><br><span class="line">                minRatio: <span class="number">1</span>, <span class="comment">// 压缩率小于1才会压缩</span></span><br><span class="line">                threshold: <span class="number">10240</span>, <span class="comment">// 对超过10k的数据压缩</span></span><br><span class="line">                deleteOriginalAssets: <span class="literal">false</span>, <span class="comment">// 是否删除未压缩的源文件，谨慎设置，如果希望提供非gzip的资源，可不设置或者设置为false（比如删除打包后的gz后还可以加载到原始资源文件）</span></span><br><span class="line">            &#125;)</span><br><span class="line">        ],</span><br><span class="line">    &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>之后在nginx配置中使用：gzip_static on，该属性能够静态加载本地的gz文件，这样就完成了gzip。向较于上一种方案，这种方法虽然上传项目文件体积更大，但免去了服务端实时的压缩过程，速度会更快。</p>
<h2 id="CDN缓存优化"><a href="#CDN缓存优化" class="headerlink" title="CDN缓存优化"></a>CDN缓存优化</h2><p>定义：CDN （Content Delivery Network，即内容分发网络）指的是一组分布在各个地区的服务器。这些服务器存储着数据的副本，因此服务器可以根据哪些服务器与用户距离最近，来满足数据的请求。 CDN 提供快速服务，较少受高流量影响。相较于其他的缓存是为了优化网页流畅程度，CDN缓存更多的是为了优化首屏加载速度。</p>
<p>CDN 的核心点有两个，一个是<strong>缓存</strong>，一个是<strong>回源</strong>。这两个概念都非常好理解。“缓存”就是说我们把资源 copy 一份到 CDN 服务器上这个过程，“回源”就是说 CDN 发现自己没有这个资源（一般是缓存的数据过期了），转头向根服务器（或者它的上层服务器）去要这个资源的过程。</p>
<p><strong>CDN 往往被用来存放静态资源</strong>。上文中我们举例所提到的“根服务器”本质上是业务服务器，它的核心任务在于<strong>生成动态页面或返回非纯静态页面</strong>，这两种过程都是需要计算的。业务服务器仿佛一个车间，车间里运转的机器轰鸣着为我们产出所需的资源；相比之下，CDN 服务器则像一个仓库，它只充当资源的“栖息地”和“搬运工”。</p>
<p>所谓“静态资源”，就是像 JS、CSS、图片等<strong>不需要业务服务器进行计算即得的资源</strong>。而“动态资源”，顾名思义是需要<strong>后端实时动态生成的资源</strong>，较为常见的就是 JSP、ASP 或者依赖服务端渲染得到的 HTML 页面。什么是“非纯静态资源”呢？它是指<strong>需要服务器在页面之外作额外计算的 HTML 页面</strong>。具体来说，当我打开某一网站之前，该网站需要通过权限认证等一系列手段确认我的身份、进而决定是否要把 HTML 页面呈现给我。这种情况下 HTML 确实是静态的，但它<strong>和业务服务器的操作耦合</strong>，我们把它丢到CDN 上显然是不合适的。</p>
<p>所以简单总结一下：静态资源走CDN便可以实现对静态资源加载的优化。同时静态资源往往并不需要 Cookie 携带什么认证信息，因此把静态资源和主页面置于不同的域名下，完美地避免了不必要的 Cookie 的出现。</p>
<p>理论的介绍大概就这么多了，在我自己的项目实践中，其实并没有把静态资源均部署在CDN上，毕竟技术力有限。只是将一些引入的公共框架代码，利用了<a href="https://www.bootcdn.cn/" target="_blank" rel="noopener">BootCDN</a>提供的免费资源进行取代。以本项目为例，我将vue、vuex、axios、echarts、elementUI均修改为CDN引入。主要的好处有两个：1、分离了公共库后，项目打包体积小了，打包速度提升了；2、使用CDN加载更加快速，且减轻了服务器压力。</p>
<p>具体实施步骤如下：1、在index.html中，添加CDN代码</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"><span class="tag">&lt;<span class="name">link</span> <span class="attr">href</span>=<span class="string">"https://cdn.bootcss.com/element-ui/2.7.2/theme-chalk/index.css"</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">"app"</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"https://cdn.bootcdn.net/ajax/libs/vue/3.0.2/vue.cjs.js"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"https://cdn.bootcdn.net/ajax/libs/vuex/4.0.0-rc.1/vuex.cjs.js"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"https://cdn.bootcdn.net/ajax/libs/vue-router/3.4.8/vue-router.common.js"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"https://cdn.bootcss.com/axios/0.18.0/axios.min.js"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.0/index.js"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"https://cdn.bootcdn.net/ajax/libs/vue-echarts/5.0.0-beta.0/vue-echarts.js"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>2.在vue.config.js中加入webpack配置代码，关于webpack配置中的externals，请<a href="https://webpack.js.org/configuration/externals/" target="_blank" rel="noopener">参考地址</a></p>
<h1 id="渲染层面的优化"><a href="#渲染层面的优化" class="headerlink" title="渲染层面的优化"></a>渲染层面的优化</h1><h2 id="服务端渲染技术"><a href="#服务端渲染技术" class="headerlink" title="服务端渲染技术"></a>服务端渲染技术</h2><p>提到渲染层面的优化就不得不说现在特别火的SSR技术了，其实它是一个相对的概念，其对立面是客户端渲染。客户端渲染就是常用的正常情况，服务端会把渲染需要的静态文件发送给客户端，客户端加载过来之后，自己在浏览器里跑一遍 JS，根据 JS 的运行结果，生成相应的 DOM。这种特性使得客户端渲染的源代码总是特别简洁。<strong>页面上呈现的内容，你在 html 源文件里里找不到</strong>——这正是它的特点。</p>
<p>服务端渲染的模式下，当用户第一次请求页面时，由服务器把需要的组件或页面渲染成 HTML 字符串，然后把它返回给客户端。客户端拿到手的，是可以直接渲染然后呈现给用户的 HTML 内容，不需要为了生成 DOM 内容自己再去跑一遍 JS 代码。使用服务端渲染的网站，可以说是“所见即所得”，<strong>页面上呈现的内容，我们在 html 源文件里也能找到</strong>。关于服务端渲染的实践方式，已经有nust.js这样的框架可以使用了，不过由于我自己的技术原因，并没有去实践一下这个新潮的技术。这里我就只说一下SSR的优缺点了，很多地方也都会提到这个。</p>
<p>优点：1、主要是出于效益的原因，因为SSR之后，搜索引擎以及各种爬虫才能够爬取网站的内容，这样才便于网站的推广。</p>
<p>2、服务端渲染解决了一个非常关键的性能问题——首屏加载速度过慢。在客户端渲染模式下，我们除了加载 HTML，还要等渲染所需的这部分 JS 加载完，之后还得把这部分 JS 在浏览器上再跑一遍。这一切都是发生在用户点击了我们的链接之后的事情，在这个过程结束之前，用户始终见不到我们网页的庐山真面目，也就是说用户一直在等！相比之下，服务端渲染模式下，服务器给到客户端的已经是一个直接可以拿来呈现给用户的网页，中间环节早在服务端就帮我们做掉了。</p>
<p>缺点：服务端渲染本质上是<strong>本该浏览器做的事情，分担给服务器去做</strong>。这样当资源抵达浏览器时，它呈现的速度就快了。乍一看好像很合理，但其实这样会成倍地增加服务端的压力，造成大量的成本，很有可能得不偿失。</p>
<h2 id="CSS选择器优化"><a href="#CSS选择器优化" class="headerlink" title="CSS选择器优化"></a>CSS选择器优化</h2><p>CSS 引擎查找样式表，对每条规则都按从右到左的顺序去匹配，与我们正常人的书写习惯刚好相反，因此在使用选择器时如果没有意识到这一点，就写出一些高性能消耗的选择器。例如: #mylist li {}。如果像这样写的话，浏览器必须遍历页面上每个 li 元素，并且每次都要去确认这个 li 元素的父元素 id 是不是 myList，这样会消耗大量性能。可以修改为：.myList_li {}同样，CSS中的通配符#会匹配所有元素，这样你使用时会让浏览器去遍历每一个元素。</p>
<p>以下为CSS书写时的性能提升方案：1、避免使用通配符，只对需要使用到的元素进行选择；2、关注可以通过继承实现的属性，避免重复匹配、重复定义；3、少使用标签选择器，尽量多使用类选择器。4、不要画蛇添足，id 和 class 选择器不应该被多余的标签选择器拖后腿。5、减少嵌套。后代选择器的开销是最高的，因此我们应该尽量将选择器的深度降到最低（最高不要超过三层），尽可能使用类来关联每一个标签元素。</p>
<h2 id="DOM优化"><a href="#DOM优化" class="headerlink" title="DOM优化"></a>DOM优化</h2><h3 id="减少回流与重绘"><a href="#减少回流与重绘" class="headerlink" title="减少回流与重绘"></a>减少回流与重绘</h3><p>重绘不一定导致回流，回流一定会导致重绘。硬要比较的话，回流比重绘做的事情更多，带来的开销也更大。定义如下：</p>
<p>回流：当我们对 DOM 的修改引发了 DOM 几何尺寸的变化（比如修改元素的宽、高或隐藏元素等）时，浏览器需要重新计算元素的几何属性（其他元素的几何属性和位置也会因此受到影响），然后再将计算的结果绘制出来。这个过程就是回流（也叫重排）。</p>
<p>重绘：当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性（比如修改了颜色或背景色）时，浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式（跳过了上图所示的回流环节）。这个过程叫做重绘。</p>
<p>1、尽量多使用变量来进行缓存跟DOM相关的数据，避免引起DOM变化；</p>
<p>2、避免逐条改变样式，使用类名去合并样式；</p>
<p>3、将 DOM “离线”：当我们给元素设置 display: none，将其从页面上“拿掉”，那么我们的后续操作，将无法触发回流与重绘——这个将元素“拿掉”的操作，就叫做 DOM 离线化。拿掉一个元素，再将他放回去，虽然会触发一次回流，但在这期间对其做的任何操作，都不会太大影响性能。</p>
<h3 id="减少获取DOM次数"><a href="#减少获取DOM次数" class="headerlink" title="减少获取DOM次数"></a>减少获取DOM次数</h3><p>在你需要多次操作并修改某个DOM时，只执行一次获取DOM的操作并将其存在变量中，这样就能节省获取DOM的性能消耗。</p>
<h3 id="减少修改DOM的次数"><a href="#减少修改DOM的次数" class="headerlink" title="减少修改DOM的次数"></a>减少修改DOM的次数</h3><p>对 DOM 的修改会引发渲染树的改变、进而去走一个（可能的）回流或重绘的过程。由于JS 的运行速度，比 DOM 快得多这个特性。我们减少 DOM 操作的核心思路，就是让 JS 去给 DOM 分压。这其实就是DOM Fragment](<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/DocumentFragment" target="_blank" rel="noopener">https://developer.mozilla.org/zh-CN/docs/Web/API/DocumentFragment</a>) 的思路。</p>
<p>DocumentFragment 接口表示一个没有父级文件的最小文档对象。它被当做一个轻量版的 Document 使用，用于存储已排好版的或尚未打理好格式的XML片段。因为 DocumentFragment 不是真实 DOM 树的一部分，它的变化不会引起 DOM 树的重新渲染的操作（reflow），且不会导致性能等问题。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> container = <span class="built_in">document</span>.getElementById(<span class="string">'container'</span>)</span><br><span class="line"><span class="comment">// 创建一个DOM Fragment对象作为容器</span></span><br><span class="line"><span class="keyword">let</span> content = <span class="built_in">document</span>.createDocumentFragment()</span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">let</span> count=<span class="number">0</span>;count&lt;<span class="number">10000</span>;count++)&#123;</span><br><span class="line">  <span class="comment">// span此时可以通过DOM API去创建</span></span><br><span class="line">  <span class="keyword">let</span> oSpan = <span class="built_in">document</span>.createElement(<span class="string">"span"</span>)</span><br><span class="line">  oSpan.innerHTML = <span class="string">'我是一个小测试'</span></span><br><span class="line">  <span class="comment">// 像操作真实DOM一样操作DOM Fragment对象</span></span><br><span class="line">  content.appendChild(oSpan)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 内容处理好了,最后再触发真实DOM的更改</span></span><br><span class="line">container.appendChild(content)</span><br></pre></td></tr></table></figure>

<p>DOM Fragment 对象允许我们像操作真实 DOM 一样去调用各种各样的 DOM API，我们的代码质量因此得到了保证。并且它的身份也非常纯粹：当我们试图将其 append 进真实 DOM 时，它会在乖乖交出自身缓存的所有后代节点后全身而退，完美地完成一个容器的使命，而不会出现在真实的 DOM 结构中。这种结构化、干净利落的特性，使得 DOM Fragment 作为经典的性能优化手段大受欢迎，这一点在 jQuery、Vue 等优秀前端框架的源码中均有体现。使用微任务队列，实现异步更新来避免过度渲染，就是用JS给DOM分压。</p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/03/06/%E5%AD%A6%E6%A0%A1%E9%A1%B9%E7%9B%AE%E5%BA%94%E7%94%A8/%E5%B1%8F%E5%B9%95%E9%80%82%E9%85%8D%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/03/06/%E5%AD%A6%E6%A0%A1%E9%A1%B9%E7%9B%AE%E5%BA%94%E7%94%A8/%E5%B1%8F%E5%B9%95%E9%80%82%E9%85%8D%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/" class="post-title-link" itemprop="url">屏幕适配解决方案</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-03-06 21:28:16" itemprop="dateCreated datePublished" datetime="2021-03-06T21:28:16+08:00">2021-03-06</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:31:40" itemprop="dateModified" datetime="2021-11-03T16:31:40+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E5%AD%A6%E6%A0%A1%E9%A1%B9%E7%9B%AE%E5%BA%94%E7%94%A8/" itemprop="url" rel="index"><span itemprop="name">学校项目应用</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>项目是基于vue框架构建的，使用了elementUI组件库和echarts插件。开发过程中需要实现的一个需求是希望能够让首页实现大屏幕的适配效果。大概的完成思路借鉴了手机淘宝之前的flexible.js，就是获取不同屏幕的宽度，然后修改html根元素的字体大小；这样跟根元素字体大小绑定的rem就能够实时地监听屏幕的变化，实现不同屏幕的适配。</p>
<p>其实移动端的适配也是同理，不过好像有很多的坑，但本项目没有移动端适配的需求，因此就暂且采用了简单的rem适配的方法。由于首页中有echarts表单，需要也能够监听屏幕的变化，因此除了flexible.js以外，我们还需要自定义一个resize.js混入在首页的每个echarts上，让其也会实时跟踪监听屏幕变化，这样才能够实现首页所有元素的屏幕适配。</p>
<h1 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h1><h2 id="基础定义"><a href="#基础定义" class="headerlink" title="基础定义"></a>基础定义</h2><p>最简单最直接的方式就是直接用百分端来设置元素的尺寸；可以实现元素大小的自适应，但无法实现字体大小的自适应，而且尺寸转为百分比计算十分麻烦。其实我们需要的是一个和屏幕宽度正相关的单位，而且这个单位要和px很容易互相转化。这样我们就可以使用这种单位进行元素尺寸和字体大小的设置。</p>
<p>em单位为相对长度单位，是根据当前元素的父元素的字体大小来计算的；但父级元素改变时，则em会经常改变，因此后面推出了rem来代替em单位的功能。</p>
<p>rem单位也是一个相对长度单位，1rem等于html元素上字体设置的大小；我们只要设置html上font-size的大小就可以改变rem所代表的大小。</p>
<p>vw、vh都是viewport视窗的相对长度单位，100vw代表着viewport视窗的宽度，100vh代表着viewport视窗的高度。</p>
<p>设备像素比device pixel ratio简称dpr，即物理像素和设备独立像素的比值，设备像素比越大意味着你的手机屏幕越高清。</p>
<p>例如：电脑的dpr都为1，而iphone7的dpr为2，因此设计稿上的1px，要想让iphone7实现适配，CSS应该为0.5px。而有的浏览器在解析0.5px 的时候会把他解析成1px,所以呈现出来会变成2px。这就是经典的1px问题。</p>
<h3 id="1px问题"><a href="#1px问题" class="headerlink" title="1px问题"></a>1px问题</h3><p>解决方案：既然1个css像素代表两个物理像素，设备又不认0.5px的写法，那就画1px，然后再想尽各种办法将线宽减少一半。</p>
<p>1、图片大法及背景渐变。这两种方案原理一样,都是设置元素一半有颜色,一半透明,比如做一个<code>2px</code>高度的图片,其中<code>1px</code>是我们想要的颜色,<code>1px</code>设置为透明。</p>
<p>2、缩放大法。这也是flexible.js的解决方案，根据对应的dpr调整对应的缩放比例,从而达到适配的目的,直接缩放页面。</p>
<p>3、使用伪元素缩放。transform: scale(1, 0.5);实现缩放的功能。</p>
<h2 id="flex弹性盒子布局"><a href="#flex弹性盒子布局" class="headerlink" title="flex弹性盒子布局"></a>flex弹性盒子布局</h2><p>当我们采用flex布局时，flex会自己根据屏幕的宽度进行适配。关于flex适配的方案比较容易，通常跟rem一起来实现屏幕宽度不同时的界面适配。这里就只介绍一下flex的基础概念，具体的布局在理解定义后较为简单，就不列举实例了。</p>
<p>简要介绍下flex常用的属性：父容器：display:flex; flex-direction用于确定flex主轴布局的方向；接下来的 justify-content, align-items, align-content 用于确定 flex 项对于 flex 容器空间的空白如何处理。</p>
<p>flex容器中的子元素会成为flex项。flex属性是flex-grow, flex-shrink, flex-basis 三个属性的简写属性。grow、shrink分别代表着增长和收缩因子；basis代表着初始基准大小。默认值为：flex: 0 1 auto。flex-basis 指定固定的长度值时，其优先级高于width；flex-basis 指定百分比值时，其参考对象是 main size.所以其计算值 flex-basis: percent * mainSize px。</p>
<h2 id="rem适配"><a href="#rem适配" class="headerlink" title="rem适配"></a>rem适配</h2><p>原理其实前面已经讲过了，就是识别不同的屏幕长宽来设置不同的html根元素的字体大小，从而用动态的rem来实现界面的配置。关键在于如何识别不同的屏幕宽度。</p>
<p>1、利用媒体查询：@media screen and (min-width:XXX)来判断设备的尺寸，进而设置html的fontSize，比较麻烦且需要考虑较多。</p>
<p>2、利用js获取并设置fontSize，简单实例如下。以下代码是以iphone6为设计稿，结果是1rem=100px的实际像素，因为iphone6的设备像素比是2所以1rem在浏览器的预览中是50px，也就是实现了1rem和设备宽度成7.5倍的关系，设备宽度改变1rem的实际大小也会改变，</p>
<p>但我自己则是用了手机淘宝开源的flexible.js，稍微修改后便实现了需求，代码比这个实例复杂许多，具体代码会贴在文章的最后。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">setRem</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">let</span> htmlRem = <span class="built_in">document</span>.documentElement.clientWidth</span><br><span class="line">        <span class="built_in">document</span>.documentElement.style.fontSize = htmlRem/<span class="number">7.5</span> + <span class="string">'px'</span></span><br><span class="line">      &#125;</span><br><span class="line">setRem()</span><br></pre></td></tr></table></figure>

<p>3、使用vm、vh：vw、vh是新的一种相对单位是把可视区域分的宽高为100份类似于百分比布局，这种方案它不用去写js，不过兼容性有点差。</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">html</span>&#123;</span><br><span class="line">    <span class="attribute">font-size</span>: <span class="number">10vw</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="px适配"><a href="#px适配" class="headerlink" title="px适配"></a>px适配</h2><p>根据不同的屏幕宽度，计算处不同的px值,所以当我们改变苹果的大小时,网站就会刷新动态计算出对应的px值,从而达到适配的目的。具体的实施代码没有找到，但其实原理逻辑相差不大。</p>
<h1 id="项目实践"><a href="#项目实践" class="headerlink" title="项目实践"></a>项目实践</h1><p>项目的适配大概有2部分吧：1、使用flexible.js来实现不同屏幕的适配；在基于vue框架的项目中，这里的flexible.js直接import进main.js即可。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params">win, lib</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> doc = win.document;</span><br><span class="line">  <span class="keyword">var</span> docEl = doc.documentElement;</span><br><span class="line">  <span class="keyword">var</span> metaEl = doc.querySelector(<span class="string">'meta[name="viewport"]'</span>);</span><br><span class="line">  <span class="keyword">var</span> flexibleEl = doc.querySelector(<span class="string">'meta[name="flexible"]'</span>);</span><br><span class="line">  <span class="keyword">var</span> dpr = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">var</span> scale = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">var</span> tid;</span><br><span class="line">  <span class="keyword">var</span> flexible = lib.flexible || (lib.flexible = &#123;&#125;);</span><br><span class="line">  <span class="comment">/*</span></span><br><span class="line"><span class="comment">  获取dom树：win.document.documentElement，后续向HTML插入dpr、font-size；</span></span><br><span class="line"><span class="comment">  分别取meta标签中元素，判断用户是否曾经设置过；viewport的meta标签，其主要用来告诉浏览器如何规范的渲染Web页面，而你则需要告诉它视窗有多大</span></span><br><span class="line"><span class="comment">   设备像素比简称为dpr，其定义了物理像素和设备独立像素的对应关系 = 物理像素 / 设备独立像素</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (metaEl) &#123;</span><br><span class="line">    <span class="built_in">console</span>.warn(<span class="string">"将根据已有的meta标签来设置缩放比例"</span>);</span><br><span class="line">    <span class="keyword">var</span> match = metaEl</span><br><span class="line">      .getAttribute(<span class="string">"content"</span>)</span><br><span class="line">      <span class="comment">// eslint-disable-next-line no-useless-escape</span></span><br><span class="line">      .match(<span class="regexp">/initial\-scale=([\d\.]+)/</span>);</span><br><span class="line">    <span class="keyword">if</span> (match) &#123;</span><br><span class="line">      scale = <span class="built_in">parseFloat</span>(match[<span class="number">1</span>]);</span><br><span class="line">      dpr = <span class="built_in">parseInt</span>(<span class="number">1</span> / scale);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (flexibleEl) &#123;</span><br><span class="line">    <span class="keyword">var</span> content = flexibleEl.getAttribute(<span class="string">"content"</span>);</span><br><span class="line">    <span class="keyword">if</span> (content) &#123;</span><br><span class="line">      <span class="comment">// eslint-disable-next-line no-useless-escape</span></span><br><span class="line">      <span class="keyword">var</span> initialDpr = content.match(<span class="regexp">/initial\-dpr=([\d\.]+)/</span>);</span><br><span class="line">      <span class="comment">// eslint-disable-next-line no-useless-escape</span></span><br><span class="line">      <span class="keyword">var</span> maximumDpr = content.match(<span class="regexp">/maximum\-dpr=([\d\.]+)/</span>);</span><br><span class="line">      <span class="keyword">if</span> (initialDpr) &#123;</span><br><span class="line">        dpr = <span class="built_in">parseFloat</span>(initialDpr[<span class="number">1</span>]);</span><br><span class="line">        scale = <span class="built_in">parseFloat</span>((<span class="number">1</span> / dpr).toFixed(<span class="number">2</span>));</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">if</span> (maximumDpr) &#123;</span><br><span class="line">        dpr = <span class="built_in">parseFloat</span>(maximumDpr[<span class="number">1</span>]);</span><br><span class="line">        scale = <span class="built_in">parseFloat</span>((<span class="number">1</span> / dpr).toFixed(<span class="number">2</span>));</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">/*</span></span><br><span class="line"><span class="comment">   这段代码是判断你的meta标签里面是不是设置了name=viewport属性，如果你设置了viewport</span></span><br><span class="line"><span class="comment">   并且设置了initial-scale（初始屏幕的大小）我们将取到这个值作为dp</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (!dpr &amp;&amp; !scale) &#123;</span><br><span class="line">    <span class="comment">// eslint-disable-next-line no-unused-vars</span></span><br><span class="line">    <span class="keyword">var</span> isAndroid = win.navigator.appVersion.match(<span class="regexp">/android/gi</span>);</span><br><span class="line">    <span class="keyword">var</span> isIPhone = win.navigator.appVersion.match(<span class="regexp">/iphone/gi</span>);</span><br><span class="line">    <span class="keyword">var</span> devicePixelRatio = win.devicePixelRatio;</span><br><span class="line">    <span class="keyword">if</span> (isIPhone) &#123;</span><br><span class="line">      <span class="comment">// iOS下，对于2和3的屏，用2倍的方案，其余的用1倍方案</span></span><br><span class="line">      <span class="keyword">if</span> (devicePixelRatio &gt;= <span class="number">3</span> &amp;&amp; (!dpr || dpr &gt;= <span class="number">3</span>)) &#123;</span><br><span class="line">        dpr = <span class="number">3</span>;</span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (devicePixelRatio &gt;= <span class="number">2</span> &amp;&amp; (!dpr || dpr &gt;= <span class="number">2</span>)) &#123;</span><br><span class="line">        dpr = <span class="number">2</span>;</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        dpr = <span class="number">1</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// 其他设备下，仍旧使用1倍的方案</span></span><br><span class="line">      dpr = <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    scale = <span class="number">1</span> / dpr;</span><br><span class="line">  &#125;</span><br><span class="line">  docEl.setAttribute(<span class="string">"data-dpr"</span>, dpr);</span><br><span class="line">  <span class="comment">/*</span></span><br><span class="line"><span class="comment">  之后如果我们动态设置了scale或者设置了meta标签里面的name＝flexible的inital-scale，</span></span><br><span class="line"><span class="comment">  那么我们就根据自己设置的dpr在判断iphone手机的retina屏幕的dpr比值判断不同型号的倍数，最后我们在html上设置了data-dpr自定义属性。</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (!metaEl) &#123;</span><br><span class="line">    metaEl = doc.createElement(<span class="string">"meta"</span>);</span><br><span class="line">    metaEl.setAttribute(<span class="string">"name"</span>, <span class="string">"viewport"</span>);</span><br><span class="line">    metaEl.setAttribute(</span><br><span class="line">      <span class="string">"content"</span>,</span><br><span class="line">      <span class="string">"initial-scale="</span> +</span><br><span class="line">        scale +</span><br><span class="line">        <span class="string">", maximum-scale="</span> +</span><br><span class="line">        scale +</span><br><span class="line">        <span class="string">", minimum-scale="</span> +</span><br><span class="line">        scale +</span><br><span class="line">        <span class="string">", user-scalable=no"</span></span><br><span class="line">    );</span><br><span class="line">    <span class="keyword">if</span> (docEl.firstElementChild) &#123;</span><br><span class="line">      docEl.firstElementChild.appendChild(metaEl);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">var</span> wrap = doc.createElement(<span class="string">"div"</span>);</span><br><span class="line">      wrap.appendChild(metaEl);</span><br><span class="line">      doc.write(wrap.innerHTML);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">/*</span></span><br><span class="line"><span class="comment">  之后当我们之前没有设置metaEl标签的话，那么需要我们手动的去创建meta标签，实现移动端的适配</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">refreshRem</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> width = docEl.getBoundingClientRect().width;</span><br><span class="line">    <span class="comment">// 最小1366px，最大适配2560px</span></span><br><span class="line">    <span class="keyword">if</span> (width / dpr &lt; <span class="number">1366</span>) &#123;</span><br><span class="line">      width = <span class="number">1366</span> * dpr;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (width / dpr &gt; <span class="number">2560</span>) &#123;</span><br><span class="line">      width = <span class="number">2560</span> * dpr;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 设置成24等份，设计稿时1920px的，这样1rem就是80px</span></span><br><span class="line">    <span class="keyword">var</span> rem = width / <span class="number">24</span>;</span><br><span class="line">    docEl.style.fontSize = rem + <span class="string">"px"</span>;</span><br><span class="line">    flexible.rem = win.rem = rem;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  win.addEventListener(</span><br><span class="line">    <span class="string">"resize"</span>,</span><br><span class="line">    <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">      clearTimeout(tid);</span><br><span class="line">      tid = setTimeout(refreshRem, <span class="number">300</span>);</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="literal">false</span></span><br><span class="line">  );</span><br><span class="line">  win.addEventListener(</span><br><span class="line">    <span class="string">"pageshow"</span>,</span><br><span class="line">    <span class="function"><span class="keyword">function</span>(<span class="params">e</span>) </span>&#123;</span><br><span class="line">      <span class="keyword">if</span> (e.persisted) &#123;</span><br><span class="line">        clearTimeout(tid);</span><br><span class="line">        tid = setTimeout(refreshRem, <span class="number">300</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="literal">false</span></span><br><span class="line">  );</span><br><span class="line">  <span class="comment">/*</span></span><br><span class="line"><span class="comment">  这段代码的目的就是监听window里面的resize和pageshow方法来实现css样式的重绘。</span></span><br><span class="line"><span class="comment">  函数里面就是实现取到当前设备的width之后根据width计算出rem的具体值，rem代表html的font-size，</span></span><br><span class="line"><span class="comment">  这里的rem代表的是一个自定义的rem，而不是rem属性！</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (doc.readyState === <span class="string">"complete"</span>) &#123;</span><br><span class="line">    doc.body.style.fontSize = <span class="number">12</span> * dpr + <span class="string">"px"</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    doc.addEventListener(</span><br><span class="line">      <span class="string">"DOMContentLoaded"</span>,</span><br><span class="line">      <span class="comment">// eslint-disable-next-line no-unused-vars</span></span><br><span class="line">      <span class="function"><span class="keyword">function</span>(<span class="params">e</span>) </span>&#123;</span><br><span class="line">        doc.body.style.fontSize = <span class="number">12</span> * dpr + <span class="string">"px"</span>;</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="literal">false</span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">/*</span></span><br><span class="line"><span class="comment">  之后我们判断document对象是否处于complete状态，</span></span><br><span class="line"><span class="comment">  如果完成状态我们给body一个font-size＝12*dpr的值，否则我们判断dom加载方法来实现body中的font-size的设置。</span></span><br><span class="line"><span class="comment">  这个设置是为了页面中字体的大小，而html中的font-size是为了设置页面的height，width等属性。</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line"></span><br><span class="line">  refreshRem();</span><br><span class="line"></span><br><span class="line">  flexible.dpr = win.dpr = dpr;</span><br><span class="line">  flexible.refreshRem = refreshRem;</span><br><span class="line">  flexible.rem2px = <span class="function"><span class="keyword">function</span>(<span class="params">d</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> val = <span class="built_in">parseFloat</span>(d) * <span class="keyword">this</span>.rem;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> d === <span class="string">"string"</span> &amp;&amp; d.match(<span class="regexp">/rem$/</span>)) &#123;</span><br><span class="line">      val += <span class="string">"px"</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> val;</span><br><span class="line">  &#125;;</span><br><span class="line">  flexible.px2rem = <span class="function"><span class="keyword">function</span>(<span class="params">d</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> val = <span class="built_in">parseFloat</span>(d) / <span class="keyword">this</span>.rem;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> d === <span class="string">"string"</span> &amp;&amp; d.match(<span class="regexp">/px$/</span>)) &#123;</span><br><span class="line">      val += <span class="string">"rem"</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> val;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;)(<span class="built_in">window</span>, <span class="built_in">window</span>[<span class="string">"lib"</span>] || (<span class="built_in">window</span>[<span class="string">"lib"</span>] = &#123;&#125;));</span><br></pre></td></tr></table></figure>

<p>2、由于首页中还有echarts组件的展示，需要自定义一个函数来监听屏幕的变化，当屏幕变化时则修改echarts组件中chart的大小；这个resize的效果需要用防抖的函数来控制resize的频率。这里我把防抖写在了一个公共库里，就能方便复用，对于resize.js，直接混入在有echarts插件的vue文件中即可。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">debounce</span>(<span class="params">func, wait, immediate</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> timeout, args, context, timestamp, result;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> later = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 据上一次触发时间间隔</span></span><br><span class="line">    <span class="keyword">const</span> last = +<span class="keyword">new</span> <span class="built_in">Date</span>() - timestamp;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait</span></span><br><span class="line">    <span class="keyword">if</span> (last &lt; wait &amp;&amp; last &gt; <span class="number">0</span>) &#123;</span><br><span class="line">      timeout = setTimeout(later, wait - last);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      timeout = <span class="literal">null</span>;</span><br><span class="line">      <span class="comment">// 如果设定为immediate===true，因为开始边界已经调用过了此处无需调用</span></span><br><span class="line">      <span class="keyword">if</span> (!immediate) &#123;</span><br><span class="line">        result = func.apply(context, args);</span><br><span class="line">        <span class="keyword">if</span> (!timeout) context = args = <span class="literal">null</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params">...args</span>) </span>&#123;</span><br><span class="line">    context = <span class="keyword">this</span>;</span><br><span class="line">    timestamp = +<span class="keyword">new</span> <span class="built_in">Date</span>();</span><br><span class="line">    <span class="keyword">const</span> callNow = immediate &amp;&amp; !timeout;</span><br><span class="line">    <span class="comment">// 如果延时不存在，重新设定延时</span></span><br><span class="line">    <span class="keyword">if</span> (!timeout) timeout = setTimeout(later, wait);</span><br><span class="line">    <span class="keyword">if</span> (callNow) &#123;</span><br><span class="line">      result = func.apply(context, args);</span><br><span class="line">      context = args = <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> &#123; debounce &#125; <span class="keyword">from</span> <span class="string">'@/XXX/XXXX'</span>;</span><br><span class="line"><span class="keyword">const</span> resizeChartMethod = <span class="string">'$__resizeChartMethod'</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  data() &#123;</span><br><span class="line">    <span class="comment">// 在组件内部将图表init的引用映射到chart属性上</span></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      chart: <span class="literal">null</span>,</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;,</span><br><span class="line">  created() &#123;</span><br><span class="line">    <span class="built_in">window</span>.addEventListener(<span class="string">'resize'</span>, <span class="keyword">this</span>[resizeChartMethod], <span class="literal">false</span>);</span><br><span class="line">  &#125;,</span><br><span class="line">  beforeDestroy() &#123;</span><br><span class="line">    <span class="built_in">window</span>.removeEventListener(<span class="string">'reisze'</span>, <span class="keyword">this</span>[resizeChartMethod]);</span><br><span class="line">  &#125;,</span><br><span class="line">  methods: &#123;</span><br><span class="line">    <span class="comment">// 通过lodash的防抖函数来控制resize的频率</span></span><br><span class="line">    [resizeChartMethod]: debounce(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">      <span class="keyword">if</span> (<span class="keyword">this</span>.chart) &#123;</span><br><span class="line">        <span class="keyword">this</span>.chart.resize();</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;, <span class="number">100</span>),</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>


      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/02/06/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BB%93/%E6%AD%A3%E5%88%99%E9%9D%A2%E8%AF%95%E9%A2%98%E6%B1%87%E6%80%BB/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/02/06/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BB%93/%E6%AD%A3%E5%88%99%E9%9D%A2%E8%AF%95%E9%A2%98%E6%B1%87%E6%80%BB/" class="post-title-link" itemprop="url">正则面试题汇总</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-02-06 21:28:16" itemprop="dateCreated datePublished" datetime="2021-02-06T21:28:16+08:00">2021-02-06</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:24:17" itemprop="dateModified" datetime="2021-11-03T16:24:17+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BB%93/" itemprop="url" rel="index"><span itemprop="name">面试总结</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>正则表达式是匹配模式，要么匹配字符，要么匹配位置。关于正则表达式的原理和写法，网上的文章已经特别之多了，本篇文章将不着重于原理与方法，将直接以各种题目的形式来进行正则表达式的介绍，同样，题目的难度也将由弱至强。首先附上经典的正则链接网站：<a href="https://jex.im/regulex/#!flags=&re=%5E(%5Cd%7B3%7D-)%7B2%7D%5Cd%7B4%7D%26" target="_blank" rel="noopener">regulex</a>。</p>
<h1 id="基本符号"><a href="#基本符号" class="headerlink" title="基本符号"></a>基本符号</h1><table>
<thead>
<tr>
<th>字符组</th>
<th>用于纵向模糊匹配，[]组内代表一个字符元素</th>
</tr>
</thead>
<tbody><tr>
<td>^</td>
<td>在第一位放脱字符，表示求反的概念</td>
</tr>
<tr>
<td>\d</td>
<td>[0-9]，digit，表示一位数字</td>
</tr>
<tr>
<td>\D</td>
<td>[^0-9]，表示除数字外的任意字符</td>
</tr>
<tr>
<td>\w</td>
<td>[0-9a-zA-Z]，word，表示数字、大小写字母和下划线等单词字母</td>
</tr>
<tr>
<td>\W</td>
<td>[^0-9a-zA-Z]，表示非单词字母</td>
</tr>
<tr>
<td>\s</td>
<td>[\t\v\n\r\f]，space，表示空白格，包括空格、水平制表符、垂直制表符、换行符、回车符、换页符</td>
</tr>
<tr>
<td>\S</td>
<td>[^ \t\v\n\r\f]，表示非空白符</td>
</tr>
<tr>
<td>.</td>
<td>[^\n\r\u2028\u2029]，通配符，表示几乎任意字符，换行、回车、行分隔、段分隔符除外。</td>
</tr>
<tr>
<td>\1</td>
<td>反向引用，代表第一个用括号区分的分组，括号嵌套的号由开括号（来判断</td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>量词</th>
<th>用于横向匹配，匹配多个字符，默认为贪婪匹配，可后加?变为惰性匹配</th>
</tr>
</thead>
<tbody><tr>
<td>{m,}</td>
<td>表示至少出现m次</td>
</tr>
<tr>
<td>？</td>
<td>等价于{0，1}，表示出现或不出现</td>
</tr>
<tr>
<td>+</td>
<td>等价于{1，}，表示出现至少一次</td>
</tr>
<tr>
<td>*</td>
<td>等价于{0，}，表示出现任意次或者不出现</td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>位置特性</th>
<th>用于位置方式的匹配</th>
</tr>
</thead>
<tbody><tr>
<td>^、$</td>
<td>脱字符、美元符号分别在多行匹配中，匹配行开头与行结尾</td>
</tr>
<tr>
<td>\b</td>
<td>匹配单词边界，具体就是\w与\W之间的位置，也包括\w与^、$之间的</td>
</tr>
<tr>
<td>\B</td>
<td>同样，是\b的反面的意思</td>
</tr>
<tr>
<td>(?=p)</td>
<td>p为一个子模式，即匹配p前面的位置</td>
</tr>
<tr>
<td>(?!p)</td>
<td>其实就是(?=p)取反，除了p前面的位置以外的所有位置</td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>方法</th>
<th>返回值与参数</th>
</tr>
</thead>
<tbody><tr>
<td>test</td>
<td>一个在字符串中测试是否匹配的RegExp方法，它返回true或false。regex.test(string)</td>
</tr>
<tr>
<td>exec</td>
<td>一个在字符串中执行查找匹配的RegExp方法，它返回一个数组（未匹配到则返回null）regex.exec(string)</td>
</tr>
<tr>
<td>match</td>
<td>一个在字符串中执行查找匹配的String方法，它返回一个数组或者在未匹配到时返回null。string.match(regex)；match返回的是一个数组，包括各个括号匹配的内容</td>
</tr>
<tr>
<td>search</td>
<td>一个在字符串中测试匹配的String方法，它返回匹配到的位置索引，或者在失败时返回-1。</td>
</tr>
<tr>
<td>replace</td>
<td>个在字符串中执行查找匹配的String方法，并且使用替换字符串替换掉匹配到的子字符串。string.replace(regex, “ ”)</td>
</tr>
<tr>
<td>split</td>
<td>一个使用正则表达式或者一个固定字符串分隔一个字符串，并将分隔后的子字符串存储到数组中的String方法。</td>
</tr>
</tbody></table>
<h1 id="简单正则匹配"><a href="#简单正则匹配" class="headerlink" title="简单正则匹配"></a>简单正则匹配</h1><h2 id="字符匹配"><a href="#字符匹配" class="headerlink" title="字符匹配"></a>字符匹配</h2><h3 id="16进制颜色值"><a href="#16进制颜色值" class="headerlink" title="16进制颜色值"></a>16进制颜色值</h3><p>要求匹配：#12f3a1，#ffBabd，#FFF；等颜色值字母不区分大小写，且可为3位或者六位</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> regex = <span class="regexp">/#([0-9a-fA-F]&#123;6&#125;|[0-9a-fA-F]&#123;3&#125;)/g</span>;</span><br><span class="line"><span class="keyword">let</span> string = <span class="string">"#12f3a1 #ffBabd #FFF #123 #586"</span>;</span><br><span class="line"><span class="built_in">console</span>.log(string.match(regex));</span><br></pre></td></tr></table></figure>

<p>相当简单的正则，主要就是把基础知识运用下，只有两个分支匹配且逻辑清楚。</p>
<h3 id="24小时时间"><a href="#24小时时间" class="headerlink" title="24小时时间"></a>24小时时间</h3><p>要求匹配：23：59，04：09，8：9，19：47；这样的时间，前面的0可以省略也可带着</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> regex = <span class="regexp">/^(0?[0-9]|1[0-9]|2[0-3]):(0?[0-9]|[1-5][0-9])$/</span>;</span><br><span class="line"><span class="keyword">let</span> arr = [<span class="string">"23:59"</span>, <span class="string">"04:09"</span>, <span class="string">"8,9"</span>, <span class="string">"19:47"</span>];</span><br><span class="line"><span class="keyword">let</span> res = [];</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; arr.length; i++) &#123;</span><br><span class="line">    <span class="keyword">if</span> (regex.test(arr[i])) &#123;</span><br><span class="line">        res.push(arr[i]);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>也是比较简单的正则，记得用括号将各段隔开，不然运算顺序可能不与你想的一致。</p>
<h3 id="IP地址"><a href="#IP地址" class="headerlink" title="IP地址"></a>IP地址</h3><p>要求匹配：192.168.225.255，156.234.156.215，1.2.3.4;类似的IP地址</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> regex = <span class="regexp">/^(25[0-5]|2[0-4]\d|[0-1]\d&#123;2&#125;|[1-9]?\d)\.\1\.\1\.\1$/</span>;</span><br><span class="line"><span class="keyword">let</span> arr = [<span class="string">"192.168.225.255"</span>, <span class="string">"156.234.156.215"</span>, <span class="string">"1.2.3.4"</span>];</span><br><span class="line"><span class="keyword">let</span> res = [];</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; arr.length; i++) &#123;</span><br><span class="line">    <span class="keyword">if</span> (regex.test(arr[i])) &#123;</span><br><span class="line">        res.push(arr[i]);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里的.字符需要转义，且利用括号分组来复用前面的正则。\1代表第一个括号引用的分组，根据第一个开括号(来确认。</p>
<h3 id="带格式日期"><a href="#带格式日期" class="headerlink" title="带格式日期"></a>带格式日期</h3><p>要求匹配：2020-09-12，2043-12-30，2018/08/09，2016.06.21；分隔符有三种可用，且要求分隔符前后使用一样。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> regex = <span class="regexp">/\d&#123;4&#125;(-|\/|\.)\d&#123;2&#125;\1\d&#123;2&#125;/</span>;</span><br><span class="line"><span class="keyword">let</span> res = [];</span><br><span class="line"><span class="keyword">let</span> arr = [<span class="string">"2020-09-12"</span>，<span class="string">"2043-12-30"</span>，<span class="string">"2018/08/09"</span>，<span class="string">"2016.06.21"</span>];</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; arr.length; i++) &#123;</span><br><span class="line">    <span class="keyword">if</span> (regex.test(arr[i])) &#123;</span><br><span class="line">        res.push(arr[i]);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>要求分隔符前后一致，因此我们就必须用上题提过的括号分组复用，才能够实现这样的功能。我这里没有对日期的期限进行要求，直接用的数字均可，有要求的话，根据之前IP例题的逻辑，也能够很容易写出，这里就不再赘述。</p>
<p>同样，分组复用可以实现简单的替换，例如：想把yyyy-mm-dd替换成mm/dd/yyyy，以下的代码可以实现</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> regex = <span class="regexp">/(\d&#123;4&#125;)-(\d&#123;2&#125;)-(\d&#123;2&#125;)/</span>;</span><br><span class="line"><span class="keyword">let</span> string = <span class="string">"2020-09-12"</span>;</span><br><span class="line"><span class="keyword">let</span> res = string.replace(regex, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">RegExp</span>.$<span class="number">2</span> + <span class="string">"/"</span> + <span class="built_in">RegExp</span>.$<span class="number">3</span> + <span class="string">"/"</span> + <span class="built_in">RegExp</span>.$<span class="number">1</span>;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">//RegExp中的$1、$2、$3代表第1、2、3个分组</span></span><br></pre></td></tr></table></figure>

<h3 id="字符串去重"><a href="#字符串去重" class="headerlink" title="字符串去重"></a>字符串去重</h3><p>实例：将”aaaaabbbbbccccc”去重返回成为”abc”。利用括号分组的同样匹配，来实现查询出重复字符的效果，由于重复字符可以出现多次，因此后面还要加上+,表示至少出现一次。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> regex = <span class="regexp">/(\w)\1+/g</span>;</span><br><span class="line"><span class="comment">//g代表全局匹配，会在一次匹配结束后继续匹配多次。而非仅匹配一次</span></span><br><span class="line"><span class="keyword">let</span> str = <span class="string">"aaaaabbbbbddddggggggggffff"</span>;</span><br><span class="line"><span class="keyword">let</span> res = str.replace(regex, <span class="string">"$1"</span>);</span><br></pre></td></tr></table></figure>



<h3 id="HTML标签"><a href="#HTML标签" class="headerlink" title="HTML标签"></a>HTML标签</h3><p>要求匹配HTML标签a内容，比如”<a herf='www.baidu.com'>“”<a>“，即匹配出两个&lt;&gt;之间，且第一个字符为a</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> regex = <span class="regexp">/&lt;a[^&gt;]+&gt;/g</span></span><br></pre></td></tr></table></figure>

<p>这里的^脱字符代表取反，即除了”&gt;”以外均匹配。</p>
<h3 id="驼峰字符串"><a href="#驼峰字符串" class="headerlink" title="驼峰字符串"></a>驼峰字符串</h3><p>要求将一个连续字符串如：get-element-by-id转化成驼峰形式</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">toHump</span>(<span class="params">str</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> str.replace(<span class="regexp">/-(\w)/g</span>, <span class="function"><span class="keyword">function</span>(<span class="params">$<span class="number">1</span></span>))</span></span><br><span class="line"><span class="function">&#125;</span></span><br></pre></td></tr></table></figure>

<p>将-后面是字符的-与首字符的位置均匹配到，然后将$1，即-后的字符变成大写后replace进去</p>
<h3 id="邮箱格式"><a href="#邮箱格式" class="headerlink" title="邮箱格式"></a>邮箱格式</h3><p>1.不限制长度</p>
<p>2.不限制大小写</p>
<p>3.邮箱开头必须是数字或字符串</p>
<p>4.邮箱中可以使用字母、数字、点号、下划线、减号，但是不能连写点号、下划线、减号，如 <a href="mailto:&#97;&#98;&#99;&#95;&#45;&#x64;&#101;&#x40;&#x71;&#x5f;&#x2e;&#x71;&#46;&#99;&#x6f;&#x6d;">&#97;&#98;&#99;&#95;&#45;&#x64;&#101;&#x40;&#x71;&#x5f;&#x2e;&#x71;&#46;&#99;&#x6f;&#x6d;</a></p>
<p>5.@符号前后不能为点号、下划线、减号</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">isAvailableEmail</span>(<span class="params">sEmail</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> reg = <span class="regexp">/^([\w+\._-])+@\w+[\.\w]+$/</span>;</span><br><span class="line">    <span class="keyword">return</span> reg.test(sEmail);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="识别十进制整数"><a href="#识别十进制整数" class="headerlink" title="识别十进制整数"></a>识别十进制整数</h3><p>修改 js 代码中 parseInt 的调用方式，使之通过全部测试用例。</p>
<p>eg：’12px’、’0x12’</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">parse2Int</span>(<span class="params">num</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> regex=<span class="regexp">/^\d+/</span>;</span><br><span class="line">    num=regex.exec(num)[<span class="number">0</span>];</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">parseInt</span>(num);</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">//或者可以直接使用parseInt(num, 10)；这个API会自动识别非十进制的数，将其排除在外</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">parse2Int</span>(<span class="params">num</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">parseInt</span>(num,<span class="number">10</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="颜色字符串转换"><a href="#颜色字符串转换" class="headerlink" title="颜色字符串转换"></a>颜色字符串转换</h3><p>考的知识点两个：</p>
<ol>
<li><p>正则表达式匹配；</p>
</li>
<li><p>toString(16)转换进制；toString()可用于转换进制、判断引用类型。</p>
</li>
</ol>
<p>做的过程中注意：</p>
<ol>
<li>数值超界（0-255）</li>
<li>不足两位补零</li>
</ol>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">rgb2hex</span>(<span class="params">sRGB</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> reg = <span class="regexp">/rgb\((\d+),\s*(\d+),\s*(\d+)\)/</span>;</span><br><span class="line">    <span class="keyword">let</span> res = sRGB.match(reg);<span class="comment">//match返回一个数组或者在未匹配到时返回null</span></span><br><span class="line">    <span class="keyword">if</span> (!res) &#123;</span><br><span class="line">        <span class="keyword">return</span> sRGB;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">let</span> str = <span class="string">'#'</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">1</span>; i &lt;= <span class="number">3</span>; i++) &#123;</span><br><span class="line">            <span class="keyword">let</span> num = <span class="built_in">parseInt</span>(res[i]);</span><br><span class="line">            <span class="keyword">if</span> (num &lt;= <span class="number">255</span> &amp;&amp; num &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">                str += (num &lt; <span class="number">16</span> ? <span class="string">'0'</span> + num.toString(<span class="number">16</span>) : num.toString(<span class="number">16</span>));</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> sRGB;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> str;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="变为驼峰"><a href="#变为驼峰" class="headerlink" title="变为驼峰"></a>变为驼峰</h3><p>replace() 方法返回一个由替换值（replacement）替换一些或所有匹配的模式（pattern）后的新字符串。</p>
<p>这个用法的本质就是：对str使用RegArg做match()匹配，如果匹配到多项结果（比如使用了全局匹配g，或者分组），那么每一个匹配结果都将执行一次FuncArg函数，并且用该函数的返回值替代源字符串中的匹配项。</p>
<p>第一个参数可以是字符串或正则表达式，如果提供的是字符串，只会替换第一个子字符串。如果想替换所有子字符串，需要提供一个指定了 g 的正则表达式。</p>
<p>第二个参数可以是字符串或函数。如果是字符串，可以使用一些特殊的 <strong>字符序列</strong>：</p>
<p>如果第二个参数也可以是函数，这个函数接收多个参数：function (match[，p1, p2, …, pn], offset, string)</p>
<ul>
<li>match：匹配的子串，等同于前面提到的 $&amp;</li>
<li>p1-p2：为捕获组对应的匹配字符串（如果设置了捕获组）。</li>
<li>offset：模式匹配项位于输入字符串的位置</li>
<li>string：输入的原始字符串。</li>
<li>函数的返回值：返回值即为替换的文本。</li>
</ul>
<p>css 中经常有类似 background-image 这种通过 - 连接的字符，通过 javascript 设置样式的时候需要将这种样式转换成 backgroundImage 驼峰格式，请完成此转换功能</p>
<p>1、以 - 为分隔符，将第二个起的非空单词首字母转为大写</p>
<p>2、-webkit-border-image 转换后的结果为 webkitBorderImage</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">cssStyle2DomStyle</span>(<span class="params">sName</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> reg = <span class="regexp">/-(\w)/g</span>;</span><br><span class="line">    <span class="keyword">let</span> res = sName.replace(reg, <span class="function"><span class="keyword">function</span>(<span class="params">fullMatch, g1, index</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (index === <span class="number">0</span>) <span class="keyword">return</span> g1;</span><br><span class="line">        <span class="comment">//当模式串匹配的为单词的首项时，证明-在最前面，此时返回小写字母，将-a替换成a。</span></span><br><span class="line">        <span class="keyword">return</span> g1.toUpperCase();</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="获取url中参数"><a href="#获取url中参数" class="headerlink" title="获取url中参数"></a>获取url中参数</h3><ol>
<li><p>指定参数名称，返回该参数的值 或者 空字符串</p>
</li>
<li><p>不指定参数名称，返回全部的参数对象 或者 {}</p>
</li>
<li><p>如果存在多个同名参数，则返回数组</p>
</li>
</ol>
<p>例:输入：<a href="http://www.nowcoder.com/?key=1&amp;key=2&amp;key=3&amp;test=4#hehe" target="_blank" rel="noopener">http://www.nowcoder.com?key=1&amp;key=2&amp;key=3&amp;test=4#hehe</a> key；输出：[1, 2, 3]</p>
<p>方法一：使用字符串拼接来匹配。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*  获取URl中的参数</span></span><br><span class="line"><span class="comment">* @para url </span></span><br><span class="line"><span class="comment">* @para key 参数名*/</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getUrlParam</span>(<span class="params">sUrl, sKey</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> left= sUrl.indexOf(<span class="string">"?"</span>) + <span class="number">1</span></span><br><span class="line">    <span class="keyword">var</span> right= sUrl.lastIndexOf(<span class="string">"#"</span>)</span><br><span class="line">    <span class="keyword">var</span> parasString = sUrl.slice(left, right)</span><br><span class="line">    <span class="keyword">var</span> paras = parasString.split(<span class="string">'&amp;'</span>);</span><br><span class="line">    <span class="keyword">var</span> parasjson = &#123;&#125;</span><br><span class="line">    paras.forEach(<span class="function"><span class="keyword">function</span> (<span class="params">value, index, arr</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">var</span> a = value.split(<span class="string">'='</span>);</span><br><span class="line">        parasjson[a[<span class="number">0</span>]] !== <span class="literal">undefined</span> ? parasjson[a[<span class="number">0</span>]] = [].concat(parasjson[a[<span class="number">0</span>]], a[<span class="number">1</span>]) : parasjson[a[<span class="number">0</span>]] = a[<span class="number">1</span>];</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> result = <span class="built_in">arguments</span>[<span class="number">1</span>] !== <span class="keyword">void</span> <span class="number">0</span> ? (parasjson[<span class="built_in">arguments</span>[<span class="number">1</span>]] || <span class="string">''</span>) : parasjson;</span><br><span class="line">    <span class="keyword">return</span> result</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>方法二：使用正则中的replace进行替换</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getUrlParam2</span>(<span class="params">sUrl, sKey</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> result, Oparam = &#123;&#125;;</span><br><span class="line">    sUrl.replace(<span class="regexp">/[\?&amp;]?(\w+)=(\w+)/g</span>, <span class="function"><span class="keyword">function</span> (<span class="params">$<span class="number">0</span>, $<span class="number">1</span>, $<span class="number">2</span></span>) </span></span><br><span class="line"><span class="function">        <span class="title">console</span>.<span class="title">log</span>(<span class="params"><span class="string">'$0:'</span> + $<span class="number">0</span> + <span class="string">"     $1:"</span> + $<span class="number">1</span> + <span class="string">"     $2:"</span> + $<span class="number">2</span></span>);</span></span><br><span class="line">        Oparam[$1] === void 0 ? Oparam[$1] = $2 : Oparam[$1] = [].concat(Oparam[$1], $2);</span><br><span class="line">    &#125;);</span><br><span class="line">    sKey === <span class="keyword">void</span> <span class="number">0</span> || sKey === <span class="string">''</span> ? result = Oparam : result = Oparam[sKey] || <span class="string">''</span>;</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>方法三：使用正则中的exec方法</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getUrlParam3</span>(<span class="params">sUrl, sKey</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> resObj = &#123;&#125;;</span><br><span class="line">    <span class="keyword">var</span> reg = <span class="regexp">/(\w+)=(\w+)/g</span>;</span><br><span class="line">    <span class="keyword">while</span> (reg.exec(sUrl)) &#123;</span><br><span class="line">        resObj[<span class="built_in">RegExp</span>.$<span class="number">1</span>] ? resObj[<span class="built_in">RegExp</span>.$<span class="number">1</span>] = [].concat(resObj[<span class="built_in">RegExp</span>.$<span class="number">1</span>], <span class="built_in">RegExp</span>.$<span class="number">2</span>) : resObj[<span class="built_in">RegExp</span>.$<span class="number">1</span>] = <span class="built_in">RegExp</span>.$<span class="number">2</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (sKey) &#123;</span><br><span class="line">        <span class="keyword">return</span> (resObj[sKey] ? resObj[sKey] : <span class="string">''</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> resObj;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="域名解析"><a href="#域名解析" class="headerlink" title="域名解析"></a>域名解析</h3><p>函数parseUrl实现将一段url字段解析为Object，例如url为：”<a href="http://www.xiyanghui.com/product/list?id=123456&amp;sort=discount#title&quot;;parseUrl(url)%E8%BF%94%E5%9B%9E%E7%9A%84%E7%BB%93%E6%9E%9C%E4%B8%BAobject%E5%A6%82%E4%B8%8B%EF%BC%9A" target="_blank" rel="noopener">http://www.xiyanghui.com/product/list?id=123456&amp;sort=discount#title&quot;;parseUrl(url)返回的结果为object如下：</a></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> object = &#123;</span><br><span class="line">    protocol:<span class="string">"http"</span>,</span><br><span class="line">    host:<span class="string">"www.xiyanghui.com"</span>,</span><br><span class="line">    path:<span class="string">"product/list"</span>,</span><br><span class="line">    query: &#123;</span><br><span class="line">        id:<span class="string">"123456"</span>,</span><br><span class="line">        sort:<span class="string">"discount"</span></span><br><span class="line">    &#125;,</span><br><span class="line">    hash:<span class="string">"title"</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">parseUrl</span>(<span class="params">str</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 判断是否传入参数</span></span><br><span class="line">    <span class="keyword">if</span> (str) &#123;</span><br><span class="line">        <span class="keyword">var</span> obj = &#123;&#125;;</span><br><span class="line">        <span class="keyword">var</span> queryArr = [];</span><br><span class="line">        <span class="comment">// 正则表达式规则</span></span><br><span class="line">        <span class="keyword">var</span> re = <span class="regexp">/^(http[s]?):\/\/([0-9a-zA-Z\.]+)\/([a-zA-Z0-9\/]+)\?([a-zA-Z0-9\=\&amp;]+)#([0-9a-zA-Z\.]+)$/</span>;</span><br><span class="line">        <span class="comment">// 利用正则表达式将字符串分组</span></span><br><span class="line">        <span class="keyword">var</span> reArr = re.exec(str);</span><br><span class="line">        <span class="keyword">if</span> (reArr) &#123;</span><br><span class="line">            obj.peotocol = reArr[<span class="number">1</span>];</span><br><span class="line">            obj.host = reArr[<span class="number">2</span>];</span><br><span class="line">            obj.path = reArr[<span class="number">3</span>];</span><br><span class="line">            queryArr = reArr[<span class="number">4</span>].split(<span class="regexp">/[\&amp;\=]+/</span>);</span><br><span class="line">            obj.query = &#123;&#125;;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; queryArr.length; i += <span class="number">2</span>) &#123;</span><br><span class="line">                obj.query[queryArr[i]] = queryArr[i + <span class="number">1</span>];</span><br><span class="line">            &#125;</span><br><span class="line">            obj.hash = reArr[<span class="number">5</span>]</span><br><span class="line">            <span class="keyword">return</span> obj;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="位置匹配"><a href="#位置匹配" class="headerlink" title="位置匹配"></a>位置匹配</h2><h3 id="千位分隔符"><a href="#千位分隔符" class="headerlink" title="千位分隔符"></a>千位分隔符</h3><p>千位分隔符的关键在于两点：1、使用量词+多次匹配d{3}；2、要求匹配的位置不能是开头，因此用(?!^)</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> regex = <span class="regexp">/(?!^)(?=(\d&#123;3&#125;)+$)/g</span>;</span><br><span class="line"><span class="keyword">let</span> regex = <span class="regexp">/(?!^)(?=(\d&#123;3&#125;)+$)/g</span>;<span class="comment">//先要求匹配的位置不能是开头,$代表从单词末尾开始匹配，/g代表</span></span><br><span class="line"><span class="keyword">let</span> string = <span class="string">"123456789"</span>;</span><br><span class="line"><span class="keyword">let</span> res = string.replace(regex, <span class="string">','</span>);</span><br></pre></td></tr></table></figure>

<p>如果还要求支持”12345783   23498237489  3219482”这样多个数字间用空格来区分的输入的话，只需要将^、$进行修改，改成\b，从而匹配多个连续字符的开头、结尾，而不是匹配整个字符串的开头、结尾。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> regex = <span class="regexp">/(?!\b)(?=(\d&#123;3&#125;)+\b)/g</span>;</span><br><span class="line"><span class="keyword">let</span> string = <span class="string">"12345783   23498237489  3219482"</span>;</span><br><span class="line"><span class="keyword">let</span> res = string.replace(regex, <span class="string">','</span>);</span><br></pre></td></tr></table></figure>

<h3 id="密码判断"><a href="#密码判断" class="headerlink" title="密码判断"></a>密码判断</h3><p>多种条件的密码判断往往写成多个小的正则进行判断，下面基本使用三个小的条件进行密码判断。总的要求是：密码长度6-12位，由数字、小写字符、大写字符组成，但必须至少包括2种字符，且必须包含数字</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//1、要求密码长度6-12位</span></span><br><span class="line"><span class="keyword">let</span> regex1 = <span class="regexp">/^[0-9A-Za-z]&#123;6,12&#125;$/</span>;</span><br><span class="line"><span class="comment">//2、要求必须包含数字</span></span><br><span class="line"><span class="keyword">let</span> regex2 = <span class="regexp">/?=.*[0-9]/</span>;<span class="comment">//即任意数量任意字符的后面前一个位置，后面会接一个数字=&gt;包含一个数字</span></span><br><span class="line"><span class="comment">//3、要求同时包含两种</span></span><br><span class="line"><span class="keyword">let</span> regex3 = <span class="regexp">/(?=.*[0-9])(?=.*[a-z])/</span>;</span><br><span class="line"><span class="keyword">let</span> arr = [<span class="string">"192.168.225.255"</span>, <span class="string">"156.234.156.215"</span>, <span class="string">"1.2.3.4"</span>];</span><br><span class="line"><span class="keyword">let</span> res = [];</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; arr.length; i++) &#123;</span><br><span class="line">    <span class="keyword">if</span> (regex1.test(arr[i]) &amp;&amp; regex2.test(arr[i]) &amp;&amp; regex3.test(arr[i])) &#123;</span><br><span class="line">        res.push(arr[i]);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="替代正则"><a href="#替代正则" class="headerlink" title="替代正则"></a>替代正则</h1><p>并不是所有的问题均适合使用正则解决：1、有些看似简单的问题，但正则做不到；2、有些能用字符串简单API就能解决的问题，使用正则会提高时间负杂度。</p>
<h2 id="提取年月日"><a href="#提取年月日" class="headerlink" title="提取年月日"></a>提取年月日</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> string = <span class="string">"2020-09-01"</span>;</span><br><span class="line"><span class="keyword">let</span> regex = <span class="regexp">/^(\d&#123;4&#125;)-(\d&#123;2&#125;)-(\d&#123;2&#125;)/</span>;</span><br><span class="line"><span class="built_in">console</span>.log(string.match(regex));</span><br><span class="line"><span class="comment">//输出为["2020-09-01", "2020", "09", "01", index: 0, input: "2020-09-01"]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//其实，用字符串的split方法来做即可：</span></span><br><span class="line"><span class="keyword">let</span> string = <span class="string">"2020-09-01"</span>;</span><br><span class="line"><span class="keyword">let</span> res = string.split(<span class="string">"-"</span>);</span><br><span class="line"><span class="built_in">console</span>.log(res);<span class="comment">//输出为["2017", "07", "01"]</span></span><br><span class="line"><span class="comment">/*split()方法用于把一个字符串分割成字符串数组。</span></span><br><span class="line"><span class="comment">separator	必需。字符串或正则表达式，从该参数指定的地方分割 stringObject。</span></span><br><span class="line"><span class="comment">howmany	可选。该参数可指定返回的数组的最大长度。如果设置了该参数，返回的子串不会多于这个参数指定的数组。如果没有设置该参数，整个字符串都会被分割，不考虑它的长度。*/</span></span><br></pre></td></tr></table></figure>

<h2 id="模糊查询"><a href="#模糊查询" class="headerlink" title="模糊查询"></a>模糊查询</h2><p>需要实现的功能是类似百度搜索框的模糊查询，这里先只考虑JS代码，构建的函数传入参数有2个，分别是list存储所有关键词信息的数组、keyword模糊查询的关键词、res查询得出的结果。</p>
<h3 id="indexOf方法"><a href="#indexOf方法" class="headerlink" title="indexOf方法"></a>indexOf方法</h3><p>stringObject.indexOf(searchValue)该方法从头到尾检索字符串stringObject，看它是否含有子串searchValue，开始检索得位置在字符串的开头，找到searchValue时，则返回其第一次出现的位置，没有找到则返回-1。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fuzzyQuery</span> (<span class="params">list, keyWord</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> res = [];</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; list.length; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (list[i].indexOf(keyWord) &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">            res.push(list[i]);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="split方法"><a href="#split方法" class="headerlink" title="split方法"></a>split方法</h3><p>stringObject.split(separator)。该方法通过在separator指定的边界处将字符串stringObject分割成子串并返回子串数组。返回的数组中的字串不包括separator自身。如果stringObject中不存在separator，将返回一个只包含stringObject的数组。故可以根据返回数组的长度来判断是否存在子字符串separator</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fuzzyQuery</span>(<span class="params">list, keyWord</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> res = [];</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; list.length; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (list[i].split(keyWord).length &gt; <span class="number">1</span>) &#123;</span><br><span class="line">            res.push(list[i]);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="match方法"><a href="#match方法" class="headerlink" title="match方法"></a>match方法</h3><p>该方法在字符串内检索指定的值，或找到一个或多个正则表达式的匹配；如果没有找到任何匹配的文本，将返回null。否则，将返回一个数组。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fuzzyQuery</span>(<span class="params">list, keyWord</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> res = [];</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; list.length; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (list[i].match(keyWord) != <span class="literal">null</span>) &#123;</span><br><span class="line">            res.push(list[i]);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="test方法"><a href="#test方法" class="headerlink" title="test方法"></a>test方法</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fuzzyQuery</span>(<span class="params">list, keyWord</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> reg = <span class="keyword">new</span> <span class="built_in">RegExp</span>(keyWord);</span><br><span class="line">    <span class="keyword">let</span> res = [];</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; list.length; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (reg.test(list[i])) &#123;</span><br><span class="line">            res.push(list[i]);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="搜索框模糊查询实现"><a href="#搜索框模糊查询实现" class="headerlink" title="搜索框模糊查询实现"></a>搜索框模糊查询实现</h2><h3 id="简单html实现"><a href="#简单html实现" class="headerlink" title="简单html实现"></a>简单html实现</h3><p>主要关注html实现，就不用上述定义的函数了，而是用更简单的方式。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">"Content-Type"</span> <span class="attr">content</span>=<span class="string">"text/html; charset=utf-8"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">title</span>&gt;</span><span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span>&gt;</span></span><br><span class="line"><span class="actionscript">onload = <span class="function"><span class="keyword">function</span> <span class="params">()</span> </span>&#123;</span></span><br><span class="line"><span class="actionscript">    <span class="comment">//onload事件，页面在加载完成时马上执行的一组代码</span></span></span><br><span class="line"><span class="actionscript">    <span class="function"><span class="keyword">function</span> <span class="title">handle</span><span class="params">()</span> </span>&#123;</span></span><br><span class="line"><span class="actionscript">        <span class="keyword">var</span> keyWords = &#123; </span></span><br><span class="line"><span class="actionscript">            <span class="string">"a"</span>: [<span class="string">"abada"</span>, <span class="string">"asdkasdfda"</span>, <span class="string">"askfdlf"</span>],</span></span><br><span class="line"><span class="actionscript">            <span class="string">"b"</span>: [<span class="string">"bfsdifdpa"</span>, <span class="string">"杨振宇"</span>, <span class="string">"杨过"</span>],</span></span><br><span class="line"><span class="actionscript">            <span class="string">"c"</span>: [<span class="string">"cdfdfgd"</span>, <span class="string">"cgfhjf"</span>, <span class="string">"cuyjk"</span>],</span></span><br><span class="line"><span class="actionscript">            <span class="string">"d"</span>:[<span class="string">"dfdgd"</span>,<span class="string">"dyjhfh"</span>,<span class="string">"dhyjgh"</span>]</span></span><br><span class="line">        &#125;;</span><br><span class="line"><span class="actionscript">        <span class="keyword">if</span> (keyWords[<span class="keyword">this</span>.value]) &#123;</span></span><br><span class="line"><span class="actionscript">            <span class="comment">//判断body中是否有这个层,如果有就删掉了</span></span></span><br><span class="line"><span class="javascript">            <span class="keyword">if</span> (<span class="built_in">document</span>.getElementById(<span class="string">'dv'</span>)) &#123;</span></span><br><span class="line"><span class="javascript">                <span class="built_in">document</span>.body.removeChild(<span class="built_in">document</span>.getElementById(<span class="string">'dv'</span>));</span></span><br><span class="line">            &#125;</span><br><span class="line"><span class="actionscript">            <span class="comment">//开始创建层</span></span></span><br><span class="line"><span class="javascript">            <span class="keyword">var</span> dvObj = <span class="built_in">document</span>.createElement(<span class="string">'div'</span>);</span></span><br><span class="line"><span class="actionscript">            dvObj.id = <span class="string">'dv'</span>;</span></span><br><span class="line"><span class="actionscript">            dvObj.style.width = <span class="string">'300px'</span>;</span></span><br><span class="line"><span class="actionscript">            <span class="comment">//dvObj.style.height = '200px'; //将来可以不要</span></span></span><br><span class="line"><span class="actionscript">            dvObj.style.border = <span class="string">'1px solid red'</span>;</span></span><br><span class="line"><span class="javascript">            <span class="built_in">document</span>.body.appendChild(dvObj);</span></span><br><span class="line"><span class="actionscript">            <span class="comment">//脱离文档流</span></span></span><br><span class="line"><span class="actionscript">            dvObj.style.position = <span class="string">'absolute'</span>;</span></span><br><span class="line"><span class="actionscript">            dvObj.style.left = <span class="keyword">this</span>.offsetLeft + <span class="string">'px'</span>;</span></span><br><span class="line"><span class="actionscript">            dvObj.style.top = <span class="keyword">this</span>.offsetHeight + <span class="keyword">this</span>.offsetTop + <span class="string">'px'</span>;</span></span><br><span class="line"><span class="actionscript">            <span class="comment">//循环创建</span></span></span><br><span class="line"><span class="actionscript">            <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; keyWords[<span class="keyword">this</span>.value].length; i++) &#123;</span></span><br><span class="line"><span class="actionscript">                <span class="comment">//创建一个可以存文本的标签</span></span></span><br><span class="line"><span class="javascript">                <span class="keyword">var</span> pObj = <span class="built_in">document</span>.createElement(<span class="string">'p'</span>);</span></span><br><span class="line"><span class="actionscript">                pObj.innerText = keyWords[<span class="keyword">this</span>.value][i];</span></span><br><span class="line"><span class="actionscript">                <span class="comment">//p标签要有小手，还有高亮显示</span></span></span><br><span class="line"><span class="actionscript">                pObj.style.cursor = <span class="string">'pointer'</span>;</span></span><br><span class="line"><span class="actionscript">                pObj.style.margin = <span class="string">'5px'</span>;</span></span><br><span class="line"><span class="actionscript">                pObj.onmouseover = <span class="function"><span class="keyword">function</span> <span class="params">()</span> </span>&#123;</span></span><br><span class="line"><span class="actionscript">                    <span class="keyword">this</span>.style.backgroundColor = <span class="string">'red'</span>;</span></span><br><span class="line">                &#125;;</span><br><span class="line"><span class="actionscript">                pObj.onmouseout = <span class="function"><span class="keyword">function</span> <span class="params">()</span> </span>&#123;</span></span><br><span class="line"><span class="actionscript">                    <span class="keyword">this</span>.style.backgroundColor = <span class="string">''</span>;</span></span><br><span class="line">                &#125;</span><br><span class="line"><span class="actionscript">                dvObj.appendChild(pObj); <span class="comment">//操作节点，把p标签加到层中</span></span></span><br><span class="line"><span class="actionscript">                <span class="comment">//同样可以用insertBefore()来添加</span></span></span><br><span class="line">            &#125;</span><br><span class="line"><span class="actionscript">            <span class="comment">//创建可以显示文件的标签</span></span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="actionscript">    <span class="comment">//firefox下检测状态改变只能用oninput,且需要用addEventListener来注册事件。 </span></span></span><br><span class="line"><span class="javascript">    <span class="keyword">if</span> (<span class="regexp">/msie/i</span>.test(navigator.userAgent)) <span class="comment">//ie浏览器 </span></span></span><br><span class="line">    &#123;</span><br><span class="line"><span class="javascript">        <span class="built_in">document</span>.getElementById(<span class="string">'txt'</span>).onpropertychange = handle</span></span><br><span class="line">    &#125;</span><br><span class="line"><span class="actionscript">    <span class="keyword">else</span> &#123;<span class="comment">//非ie浏览器，比如Firefox </span></span></span><br><span class="line"><span class="javascript">        <span class="built_in">document</span>.getElementById(<span class="string">'txt'</span>).addEventListener(<span class="string">"input"</span>, handle, <span class="literal">false</span>);</span></span><br><span class="line"><span class="actionscript">        <span class="comment">//绑定事件对象.addEventListener(事件类型，回调函数，bool值)</span></span></span><br><span class="line"><span class="actionscript">        <span class="comment">//如果不传入bool值，或者为false；事件就会走冒泡阶段；反之，事件会走捕获阶段。</span></span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">span</span> <span class="attr">id</span>=<span class="string">"msg"</span>&gt;</span><span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">请输入搜索关键字</span><br><span class="line"><span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">name</span>=<span class="string">"name"</span> <span class="attr">value</span>=<span class="string">""</span> <span class="attr">style</span>=<span class="string">"width:300px;height:30px;font-size:25px; border:1px solid green"</span> <span class="attr">id</span>=<span class="string">"txt"</span>/&gt;</span>百度一下</span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<h3 id="利用JSONP调用百度接口"><a href="#利用JSONP调用百度接口" class="headerlink" title="利用JSONP调用百度接口"></a>利用JSONP调用百度接口</h3><p>JSONP(JSONwith Padding)是一个非官方的协议，它允许在服务器端集成Script tags返回至客户端，通过javascript callback的形式实现跨域访问（这仅仅是JSONP简单的实现形式）。 该代码实现搬运自<a href="https://blog.csdn.net/zfz5720/article/details/78044439" target="_blank" rel="noopener">CSDN</a></p>
<p>实现原理：向输入框动态输入时关键词，将当前关键词作为问号参数后面的值，因为要跨域使用百度的接口，所以通过 JSONP 跨域创建 Ajax 请求。回调函数处理返回值。</p>
<p>1.使用 flex 布局实现搜索框的水平垂直居中。</p>
<p>2.先获取常用的 DOM 节点，避免后续频繁查询操作 DOM。</p>
<p>3.为了避免在输入过程中频繁发送请求(如果打字速度快),对请求函数做了函数节流，调了一下间隔 130ms 差不多正好，时间再长就会有卡顿的感觉。使用了 ES6 中的箭头函数避免了setTimeout 中 this 指向的问题。</p>
<p>4.在回调函数中：</p>
<ul>
<li>每一次执行时首先要清除建议框里的内容，不然上一次的结果还会存在建议框里！截取了结果中的前五个(如果把所有结果都展示出来感觉有点丑…百度官方是展示前四个搜索建议)</li>
<li>结果处理完毕后，执行自执行匿名函数，删除创建的 script 标签;</li>
</ul>
<p>5.由于 li 是动态创建的，点击 li 标签或者点击”搜索一下”跳转百度进行搜索时，利用事件冒泡原理，进行事件委托。这里没有考虑兼容性问题：</p>
<p>6.除了点击事件，键盘事件–回车键以及上下键都是进行事件委托进行注册的。最终能够实现键盘上下键鼠标选择，点击“搜索一下”或回车键实现跳转搜索。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"en"</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"viewport"</span> <span class="attr">content</span>=<span class="string">"width=device-width, initial-scale=1.0"</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!-- 兼容性视图 --&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">"X-UA-Compatible"</span> <span class="attr">content</span>=<span class="string">"ie=edge"</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">content</span>=<span class="string">"更方便快捷搜索，从而达到事半功倍的效果"</span> <span class="attr">name</span>=<span class="string">"description"</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">title</span>&gt;</span>search you want<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">style</span>&gt;</span></span><br><span class="line">    html &#123;</span><br><span class="line">      height: 100%;</span><br><span class="line">    &#125;</span><br><span class="line">    body &#123;</span><br><span class="line"><span class="css">      <span class="selector-tag">background</span>: <span class="selector-id">#f0f3ef</span>;</span></span><br><span class="line">      height: 100%;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="css">    <span class="selector-class">.container</span> &#123;</span></span><br><span class="line">      height: 100%;</span><br><span class="line">      display: flex;</span><br><span class="line">      justify-content: center;</span><br><span class="line">      align-items: center;</span><br><span class="line">      flex-direction: column;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="css">    <span class="selector-class">.bgDiv</span> &#123;</span></span><br><span class="line">      box-sizing: border-box;</span><br><span class="line">      width: 595px;</span><br><span class="line">      height: 55px;</span><br><span class="line">      position: relative;</span><br><span class="line">    /* position: absolute;</span><br><span class="line">    left: 50%;</span><br><span class="line">    top: 50%;</span><br><span class="line">    transform: translate(-50%, -50%); */</span><br><span class="line">    &#125;</span><br><span class="line"><span class="css">    <span class="selector-class">.search-input-text</span> &#123;</span></span><br><span class="line"><span class="css">      <span class="selector-tag">border</span>: 1<span class="selector-tag">px</span> <span class="selector-tag">solid</span> <span class="selector-id">#b6b6b6</span>;</span></span><br><span class="line">      width: 495px;</span><br><span class="line"><span class="css">      <span class="selector-tag">background</span>: <span class="selector-id">#fff</span>;</span></span><br><span class="line">      height: 33px;</span><br><span class="line">      line-height: 33px;</span><br><span class="line">      font-size: 18px;</span><br><span class="line">      padding: 3px 0 0 7px;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="css">    <span class="selector-class">.search-input-button</span> &#123;</span></span><br><span class="line">      width: 90px;</span><br><span class="line">      height: 38px;</span><br><span class="line"><span class="css">      <span class="selector-tag">color</span>: <span class="selector-id">#fff</span>;</span></span><br><span class="line">      font-size: 16px;</span><br><span class="line">      letter-spacing: 3px;</span><br><span class="line"><span class="css">      <span class="selector-tag">background</span>: <span class="selector-id">#3385ff</span>;</span></span><br><span class="line"><span class="css">      <span class="selector-tag">border</span>: <span class="selector-class">.5px</span> <span class="selector-tag">solid</span> <span class="selector-id">#2d78f4</span>;</span></span><br><span class="line">      margin-left: -5px;</span><br><span class="line">      vertical-align: top;</span><br><span class="line"><span class="css">      <span class="selector-tag">opacity</span>: <span class="selector-class">.9</span>;</span></span><br><span class="line">    &#125;</span><br><span class="line"><span class="css">    <span class="selector-class">.search-input-button</span><span class="selector-pseudo">:hover</span> &#123;</span></span><br><span class="line">      opacity: 1;</span><br><span class="line"><span class="css">      <span class="selector-tag">box-shadow</span>: 0 1<span class="selector-tag">px</span> 1<span class="selector-tag">px</span> <span class="selector-id">#333</span>;</span></span><br><span class="line">      cursor: pointer;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="css">    <span class="selector-class">.suggest</span> &#123;</span></span><br><span class="line">      width: 502px;</span><br><span class="line">      position: absolute;</span><br><span class="line">      top: 38px;</span><br><span class="line"><span class="css">      <span class="selector-tag">border</span>: 1<span class="selector-tag">px</span> <span class="selector-tag">solid</span> <span class="selector-id">#999</span>;</span></span><br><span class="line"><span class="css">      <span class="selector-tag">background</span>: <span class="selector-id">#fff</span>;</span></span><br><span class="line">      display: none;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="css">    <span class="selector-class">.suggest</span> <span class="selector-tag">ul</span> &#123;</span></span><br><span class="line">      list-style: none;</span><br><span class="line">      margin: 0;</span><br><span class="line">      padding: 0;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="css">    <span class="selector-class">.suggest</span> <span class="selector-tag">ul</span> <span class="selector-tag">li</span> &#123;</span></span><br><span class="line">      padding: 3px;</span><br><span class="line">      font-size: 17px;</span><br><span class="line">      line-height: 25px;</span><br><span class="line">      cursor: pointer;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="css">    <span class="selector-class">.suggest</span> <span class="selector-tag">ul</span> <span class="selector-tag">li</span><span class="selector-pseudo">:hover</span> &#123;</span></span><br><span class="line"><span class="css">      <span class="selector-tag">background-color</span>: <span class="selector-id">#e5e5e5</span></span></span><br><span class="line">    &#125;</span><br><span class="line">  <span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"container"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"bgDiv"</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">class</span>=<span class="string">"search-input-text"</span> <span class="attr">value</span>=<span class="string">""</span> <span class="attr">autofocus</span> <span class="attr">placeholder</span>=<span class="string">"关键词"</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">"button"</span> <span class="attr">value</span>=<span class="string">"搜索一下"</span> <span class="attr">class</span>=<span class="string">"search-input-button"</span> <span class="attr">id</span>=<span class="string">"btn"</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"suggest"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">ul</span> <span class="attr">id</span>=<span class="string">"search-result"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="javascript">    <span class="keyword">var</span> suggestContainer = <span class="built_in">document</span>.getElementsByClassName(<span class="string">"suggest"</span>)[<span class="number">0</span>];</span></span><br><span class="line"><span class="javascript">    <span class="keyword">var</span> searchInput = <span class="built_in">document</span>.getElementsByClassName(<span class="string">"search-input-text"</span>)[<span class="number">0</span>];</span></span><br><span class="line"><span class="javascript">    <span class="keyword">var</span> bgDiv = <span class="built_in">document</span>.getElementsByClassName(<span class="string">"bgDiv"</span>)[<span class="number">0</span>];</span></span><br><span class="line"><span class="javascript">    <span class="keyword">var</span> searchResult = <span class="built_in">document</span>.getElementById(<span class="string">"search-result"</span>);</span></span><br><span class="line"></span><br><span class="line"><span class="actionscript">    <span class="comment">// 清除建议框内容</span></span></span><br><span class="line"><span class="actionscript">    <span class="function"><span class="keyword">function</span> <span class="title">clearContent</span><span class="params">()</span> </span>&#123;</span></span><br><span class="line"><span class="actionscript">      <span class="keyword">var</span> size = searchResult.childNodes.length;</span></span><br><span class="line"><span class="actionscript">        <span class="comment">//childNodes方法返回数组，根据数组长度判断建议框的长度</span></span></span><br><span class="line"><span class="actionscript">      <span class="keyword">for</span> (<span class="keyword">var</span> i = size - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span></span><br><span class="line">        searchResult.removeChild(searchResult.childNodes[i]);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;;</span><br><span class="line"><span class="actionscript">	<span class="comment">// 回调函数处理返回值</span></span></span><br><span class="line"><span class="actionscript">    <span class="function"><span class="keyword">function</span> <span class="title">handleSuggestion</span><span class="params">(res)</span> </span>&#123;</span></span><br><span class="line"><span class="actionscript">      <span class="comment">// 清空之前的数据！！</span></span></span><br><span class="line">      clearContent();</span><br><span class="line"><span class="actionscript">      <span class="keyword">var</span> result = res.s;</span></span><br><span class="line"><span class="actionscript">      <span class="comment">// 截取前五个搜索建议项</span></span></span><br><span class="line">      if (result.length &gt; 4) &#123;</span><br><span class="line">        result = result.slice(0, 5)</span><br><span class="line">      &#125;</span><br><span class="line"><span class="javascript">      <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; result.length; i++) &#123;</span></span><br><span class="line"><span class="actionscript">        <span class="comment">// 动态创建li标签</span></span></span><br><span class="line"><span class="javascript">        <span class="keyword">var</span> liObj = <span class="built_in">document</span>.createElement(<span class="string">"li"</span>);</span></span><br><span class="line">        liObj.innerHTML = result[i];</span><br><span class="line">        searchResult.appendChild(liObj);</span><br><span class="line">      &#125;</span><br><span class="line"><span class="actionscript">      <span class="comment">// 自执行匿名函数--删除用于跨域的script标签</span></span></span><br><span class="line"><span class="actionscript">      (<span class="function"><span class="keyword">function</span> <span class="params">()</span> </span>&#123;</span></span><br><span class="line"><span class="javascript">        <span class="keyword">var</span> s = <span class="built_in">document</span>.querySelectorAll(<span class="string">'script'</span>);</span></span><br><span class="line"><span class="actionscript">        <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">1</span>, len = s.length; i &lt; len; i++) &#123;</span></span><br><span class="line"><span class="javascript">          <span class="built_in">document</span>.body.removeChild(s[i]);</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;)()</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line"><span class="actionscript">    <span class="function"><span class="keyword">function</span> <span class="title">jumpPage</span><span class="params">()</span> </span>&#123;</span></span><br><span class="line"><span class="javascript">      <span class="built_in">window</span>.open(<span class="string">`https://www.baidu.com/s?word=<span class="subst">$&#123;<span class="built_in">encodeURI</span>(searchInput.value)&#125;</span>`</span>);</span></span><br><span class="line">    &#125;  </span><br><span class="line">     </span><br><span class="line"><span class="actionscript">    <span class="keyword">var</span> timer = <span class="literal">null</span>;</span></span><br><span class="line"><span class="actionscript">    <span class="comment">// 注册输入框键盘抬起事件</span></span></span><br><span class="line"><span class="actionscript">    searchInput.onkeyup = <span class="function"><span class="keyword">function</span> <span class="params">(e)</span> </span>&#123;</span></span><br><span class="line"><span class="actionscript">      suggestContainer.style.display = <span class="string">"block"</span>;</span></span><br><span class="line"><span class="actionscript">      <span class="comment">// 如果输入框内容为空 清除内容且无需跨域请求</span></span></span><br><span class="line"><span class="actionscript">      <span class="keyword">if</span> (<span class="keyword">this</span>.value.length === <span class="number">0</span>) &#123;</span></span><br><span class="line">        clearContent();</span><br><span class="line"><span class="actionscript">        <span class="keyword">return</span>;</span></span><br><span class="line">      &#125;</span><br><span class="line"><span class="actionscript">      <span class="keyword">if</span> (<span class="keyword">this</span>.timer) &#123;</span></span><br><span class="line"><span class="actionscript">        clearTimeout(<span class="keyword">this</span>.timer);</span></span><br><span class="line">      &#125;</span><br><span class="line">      if (e.keyCode !== 40 &amp;&amp; e.keyCode !== 38) &#123;</span><br><span class="line"><span class="actionscript">        <span class="comment">// 函数节流优化</span></span></span><br><span class="line"><span class="javascript">        <span class="keyword">this</span>.timer = setTimeout(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="actionscript">          <span class="comment">// 创建script标签JSONP跨域</span></span></span><br><span class="line"><span class="javascript">          <span class="keyword">var</span> script = <span class="built_in">document</span>.createElement(<span class="string">"script"</span>);</span></span><br><span class="line"><span class="javascript">          script.src = <span class="string">"https://www.baidu.com/su?&amp;wd="</span> + <span class="built_in">encodeURI</span>(<span class="keyword">this</span>.value.trim()) +</span></span><br><span class="line"><span class="actionscript">            <span class="string">"&amp;p=3&amp;cb=handleSuggestion"</span>;</span></span><br><span class="line"><span class="javascript">          <span class="built_in">document</span>.body.appendChild(script);</span></span><br><span class="line">        &#125;, 130)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;;</span><br><span class="line"><span class="actionscript">    <span class="comment">// 事件委托 点击li标签或者点击搜索按钮跳转到百度搜索页面</span></span></span><br><span class="line"><span class="actionscript">    bgDiv.addEventListener(<span class="string">"click"</span>, <span class="function"><span class="keyword">function</span> <span class="params">(e)</span> </span>&#123;</span></span><br><span class="line"><span class="actionscript">      <span class="keyword">if</span> (e.target.nodeName.toLowerCase() === <span class="string">'li'</span>) &#123;</span></span><br><span class="line"><span class="actionscript">        <span class="keyword">var</span> keywords = e.target.innerText;</span></span><br><span class="line">        searchInput.value = keywords;</span><br><span class="line">        jumpPage();</span><br><span class="line"><span class="actionscript">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (e.target.id === <span class="string">'btn'</span>) &#123;</span></span><br><span class="line">        jumpPage();</span><br><span class="line">      &#125;</span><br><span class="line"><span class="actionscript">    &#125;, <span class="literal">false</span>);</span></span><br><span class="line"></span><br><span class="line"><span class="actionscript">    <span class="keyword">var</span> i = <span class="number">0</span>;</span></span><br><span class="line"><span class="actionscript">    <span class="keyword">var</span> flag = <span class="number">1</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="actionscript">    <span class="comment">// 事件委托 监听键盘事件</span></span></span><br><span class="line"><span class="actionscript">    bgDiv.addEventListener(<span class="string">"keydown"</span>, <span class="function"><span class="keyword">function</span> <span class="params">(e)</span> </span>&#123;</span></span><br><span class="line"><span class="actionscript">      <span class="keyword">var</span> size = searchResult.childNodes.length;</span></span><br><span class="line">      if (e.keyCode === 13) &#123;</span><br><span class="line">        jumpPage();</span><br><span class="line">      &#125;;</span><br><span class="line"><span class="actionscript">      <span class="comment">// 键盘向下事件</span></span></span><br><span class="line">      if (e.keyCode === 40) &#123;</span><br><span class="line">        if (flag === 0) &#123;</span><br><span class="line">          i = i + 2;</span><br><span class="line">        &#125;</span><br><span class="line">        flag = 1;</span><br><span class="line">        e.preventDefault();</span><br><span class="line">        if (i &gt;= size) &#123;</span><br><span class="line">          i = 0;</span><br><span class="line">        &#125;</span><br><span class="line">        if (i &lt; size) &#123;</span><br><span class="line">          searchInput.value = searchResult.childNodes[i++].innerText;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;;</span><br><span class="line"><span class="actionscript">      <span class="comment">// 键盘向上事件</span></span></span><br><span class="line">      if (e.keyCode === 38) &#123;</span><br><span class="line">        if (flag === 1) &#123;</span><br><span class="line">          i = i - 2;</span><br><span class="line">        &#125;</span><br><span class="line">        flag = 0;</span><br><span class="line">        e.preventDefault();</span><br><span class="line">        if (i &lt; 0) &#123;</span><br><span class="line">          i = size - 1;</span><br><span class="line">        &#125;</span><br><span class="line">        if (i &gt; -1) &#123;</span><br><span class="line">          searchInput.value = searchResult.childNodes[i--].innerText;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;;</span><br><span class="line"><span class="actionscript">    &#125;, <span class="literal">false</span>);</span></span><br><span class="line"></span><br><span class="line"><span class="actionscript">    <span class="comment">// 点击页面任何其他地方 搜索结果框消失</span></span></span><br><span class="line"><span class="javascript">    <span class="built_in">document</span>.onclick = <span class="function"><span class="params">()</span> =&gt;</span> clearContent()</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>


      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/01/09/%E5%89%8D%E7%AB%AF/DOM%E9%97%AE%E9%A2%98%E6%B1%87%E6%80%BB/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/01/09/%E5%89%8D%E7%AB%AF/DOM%E9%97%AE%E9%A2%98%E6%B1%87%E6%80%BB/" class="post-title-link" itemprop="url">DOM问题汇总</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-01-09 20:02:33" itemprop="dateCreated datePublished" datetime="2021-01-09T20:02:33+08:00">2021-01-09</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:25:26" itemprop="dateModified" datetime="2021-11-03T16:25:26+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E5%89%8D%E7%AB%AF%E9%A1%B5%E9%9D%A2/" itemprop="url" rel="index"><span itemprop="name">前端页面</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>直接操作DOM已经很少在业务场景中用到了，但DOM始终作为前端的基础之一，在面试中往往总能够遇到。本文希望能够汇总以下简单的DOM知识点，让自己在沉迷框架构建时，不至于忽略或者忘记最基础的知识。主要分为两部分：1、常用的DOM方法的汇总；2、DOM的事件机制，主要是事件冒泡与事件捕获。</p>
<h1 id="DOM方法"><a href="#DOM方法" class="headerlink" title="DOM方法"></a>DOM方法</h1><h2 id="选取元素"><a href="#选取元素" class="headerlink" title="选取元素"></a>选取元素</h2><h3 id="名称选择"><a href="#名称选择" class="headerlink" title="名称选择"></a>名称选择</h3><p>名称选择比较简单，主要是根据ID、name属性、标签名称、类名；来选择对应的DOM节点。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//ID选择器：基于id=""</span></span><br><span class="line"><span class="keyword">let</span> id =  <span class="built_in">document</span>.getElementById(<span class="string">"id"</span>);</span><br><span class="line"><span class="comment">//名称选择器:基于name属性</span></span><br><span class="line"><span class="keyword">let</span> name = <span class="built_in">document</span>.getElementsByName(<span class="string">"name"</span>);</span><br><span class="line"><span class="comment">//标签选择器：利用HTML元素的标签名称选取指定类型的元素</span></span><br><span class="line"><span class="keyword">var</span> h1 = <span class="built_in">document</span>.getElementsByTagName(<span class="string">"h1"</span>);</span><br><span class="line"><span class="comment">//类选择器：利用HTML的class属性值选择元素</span></span><br><span class="line"><span class="keyword">let</span> title = <span class="built_in">document</span>.getElementsByClassName(title);</span><br></pre></td></tr></table></figure>

<h3 id="CSS选择"><a href="#CSS选择" class="headerlink" title="CSS选择"></a>CSS选择</h3><p>通过CSS样式表选择器的强大语法，也可以来选择元素，返回第一个匹配的元素，或者返回元素数组。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> title = <span class="built_in">document</span>.querySelector(<span class="string">"#title"</span>);   <span class="comment">// CSS ID选择</span></span><br><span class="line"><span class="keyword">var</span> h1 = <span class="built_in">document</span>.querySelector(<span class="string">"h1"</span>);     <span class="comment">//选取第一个h1元素</span></span><br><span class="line"><span class="keyword">var</span> h1s = <span class="built_in">document</span>.querySelectorAll(<span class="string">"h1"</span>);   <span class="comment">//返回所有h1标签元素</span></span><br></pre></td></tr></table></figure>

<h2 id="相近节点选取"><a href="#相近节点选取" class="headerlink" title="相近节点选取"></a>相近节点选取</h2><p>节点：页面中所有的内容都是节点（标签，属性，文本：文字，空格，换行）文档：document—-页面中的顶级对象元素：页面中所有的标签，标签–元素–对象（通过DOM的方式来获取这个标签，得到了这个对象，此时这个对象叫DOM对象）。</p>
<p>关于节点的选取有如下的方法：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">h1.parentNode;<span class="comment">//父节点</span></span><br><span class="line">h1.childNodes;<span class="comment">//以数组形式返回子节点</span></span><br><span class="line">h1.firstChild; h1.lastChild;</span><br><span class="line">h1.nextSibling;<span class="comment">//下一个兄弟节点</span></span><br><span class="line">h1.previousSibling;<span class="comment">//前一个兄弟节点</span></span><br><span class="line">h1.nodeType;</span><br><span class="line"><span class="comment">//返回节点类型的数字表示：1-element节点；3-text节点；8-comment节点；9-document节点；11-documentFragment节点</span></span><br><span class="line">h1.nodeValue;<span class="comment">//返回Text 节点 或 Comment 节点的值</span></span><br><span class="line">h1.nodeName;<span class="comment">//返回元素的标签名，以大写形式表示</span></span><br></pre></td></tr></table></figure>

<p>元素相关的选取同样有如下的方法：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">h2.children;<span class="comment">//以数组的形式返回所有的子元素</span></span><br><span class="line">h2.firstElementChild; h2.lastElementChild;<span class="comment">//返回首子元素与尾子元素</span></span><br><span class="line">h2.nextElementSibling; h2.previousElementSibling;<span class="comment">//返回上一兄弟元素与下一兄弟元素</span></span><br><span class="line">h2.childElementCount;<span class="comment">//返回子元素数量</span></span><br></pre></td></tr></table></figure>

<h2 id="属性相关"><a href="#属性相关" class="headerlink" title="属性相关"></a>属性相关</h2><p>表示HTML文档元素的HTMLElement对象定义了读/写属性，它们对应于元素的HTML属性。 HTMLElement定义的通用HTML属性，包括id、lang、dir、事件处理程序onclick及表单相关属性等。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">h3.getAttribute(<span class="string">"width"</span>);<span class="comment">//返回非标准的HTML属性的值</span></span><br><span class="line">h3.setAttribute(<span class="string">"width"</span>, <span class="string">"150px"</span>);<span class="comment">//设置非标准的HTML属性的值</span></span><br><span class="line">h3.hasAttribute(<span class="string">"height"</span>);<span class="comment">//判断属性是否存在</span></span><br><span class="line">h3.removeAttribute(<span class="string">"width"</span>);<span class="comment">//删除某一属性</span></span><br><span class="line"><span class="comment">//在HTML5文档中，任意以 data- 为前缀的小写的属性名字都是合法的。这些 “数据集属性” 定义了一种标准的、附加额外数据的方法</span></span><br><span class="line"><span class="comment">//以data-x = ""为例</span></span><br><span class="line">h3.dataset.x;</span><br><span class="line"><span class="comment">//Node节点定义了 attributes 属性，针对 Element 对象，attributes 是元素所有属性的类数组对象</span></span><br><span class="line"><span class="comment">//索引 attributes 对象得到的值是 Attr 对象。Attr 的 name 和 value 返回该属性的名字和值</span></span><br><span class="line"><span class="keyword">let</span> a = h3.attributes.src.value;</span><br><span class="line"></span><br><span class="line">h4.innerHTML;<span class="comment">//以字符串形式返回这个元素的内容。 也可以用来替换元素当前内容</span></span><br><span class="line">h4.outerHTML;<span class="comment">//以字符串形式返回这个元素及内容。 也可以用来替换元素当前内容</span></span><br><span class="line">h4.textContent;<span class="comment">//查询或替换纯文本元素内容的标准方法是用Node的textContent属性来实现。</span></span><br></pre></td></tr></table></figure>

<h2 id="创建节点"><a href="#创建节点" class="headerlink" title="创建节点"></a>创建节点</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">document</span>.createElement(<span class="string">"h1"</span>);<span class="comment">//使用document 对象的createElement () 方法创建新的Element节点</span></span><br><span class="line"><span class="built_in">document</span>.createTextNode(<span class="string">"文本节点"</span>);<span class="comment">//创建纯文本节点</span></span><br><span class="line"><span class="built_in">document</span>.createDocumentFragment();<span class="comment">//创建文档片段，往往有更好性能</span></span><br><span class="line"><span class="comment">//因为文档片段存在于内存中，并不在Dom树中，所以将子元素插入到文档片段时不会引起页面回流 （对元素位置和几何上计算）</span></span><br><span class="line"><span class="built_in">document</span>.createCmoment(<span class="string">"...."</span>);<span class="comment">//创建注释节点</span></span><br><span class="line">h4.cloneNode(<span class="literal">true</span>);<span class="comment">//通过复制已存在的节点来创建新的文档节点。传参数true表示深克隆，false表示浅复制</span></span><br></pre></td></tr></table></figure>

<h2 id="插入、修改节点"><a href="#插入、修改节点" class="headerlink" title="插入、修改节点"></a>插入、修改节点</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">h5.appendChild(<span class="string">"h1"</span>);<span class="comment">//在指定元素上插入子节点，并使其成为该节点的最后一个子节点</span></span><br><span class="line"><span class="comment">//一般先新建子节点，再插入子节点</span></span><br><span class="line">h5.insertBefore(<span class="string">"h1"</span>, <span class="string">"h2"</span>);</span><br><span class="line"><span class="comment">//1. 在父节点上调用本方法2. 第一参数表示待插入的节点</span></span><br><span class="line"><span class="comment">//3. 第二参数是父节点中已经存在的子节点，新节点插入到该节点的前面</span></span><br><span class="line">h5.removeChild(<span class="string">"h2"</span>);<span class="comment">//在父节点中调用，参数是待删除的节点</span></span><br><span class="line">h5.replaceChild(<span class="string">"h2,"</span>, <span class="string">"h2"</span>);</span><br><span class="line"><span class="comment">//1. 在父节点上调用;2. 第一参数是新节点;3. 第二个参数是需要替换的节点</span></span><br></pre></td></tr></table></figure>

<h1 id="DOM事件机制"><a href="#DOM事件机制" class="headerlink" title="DOM事件机制"></a>DOM事件机制</h1><p>1、事件是在编程时系统内发生的动作或者发生的事情</p>
<p>2、事件是要绑定在元素上的。比如给一个div元素绑定一个鼠标悬浮事件，给一个ol元素绑定鼠标单击事件。</p>
<p>3、可以使用事件监听函数（也叫事件处理程序、侦听器）来监听事件，以便事件发生时执行相应的代码</p>
<p>事件发生时元素节点之间按照特定的顺序传播，这个过程即DOM事件流，描述的是从页面接收事件的顺序。</p>
<h2 id="冒泡与捕获"><a href="#冒泡与捕获" class="headerlink" title="冒泡与捕获"></a>冒泡与捕获</h2><p>首先开始事件捕获阶段：从DOM树最根部的节点<code>window</code>开始，沿着DOM树向下遍历每个元素，直到触发元素目标元素target。如果这些元素也注册了click事件（且为捕获阶段），就会执行他们相应的事件监听函数。即从上到下触发父元素对应的事件。在事件捕获这一阶段，为截获事件提供了机会。</p>
<p>当前目标阶段：实际的目标接收到，并执行对应得事件监听函数。</p>
<p>事件冒泡阶段：从触发元素目标元素target开始，向上逆着遍历DOM树，直到最根部<code>window</code>元素。如果这些元素也注册了click事件（且为冒泡阶段），就会执行他们相应的事件监听函数</p>
<p>我们在使用 addEventListener 监听事件时，addEventListener(‘click’, fn, bool)如果第三个参数 bool 不传，或者传 false, 那么我们会在冒泡阶段调用 fn如果第三个参数 Bool 传值为 true, 那么我们会在捕获阶段调用 fn。因此，默认是在冒泡阶段来监听事件的。</p>
<p>捕获不可以取消，但是冒泡可以取消，e.propagation()就可但是有一些事件不可以取消冒泡，比如 scroll 事件。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">span</span>&gt;</span>文字<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">e.target 用户正在操作的元素</span><br><span class="line">e.currentTarget 程序员在监听的元素</span><br><span class="line">假设我们监听的是 div, 但用户实际点击的是文字，那么e.target 就是 span 标签,e.currentTarget 就是 div 标签。</span><br></pre></td></tr></table></figure>

<h2 id="事件委托"><a href="#事件委托" class="headerlink" title="事件委托"></a>事件委托</h2><p>冒泡阶段，浏览器从用户点击的内容从下往上遍历至 window，逐个触发事件处理函数，因此可以监听一个祖先节点(例如爸爸节点、爷爷节点)来同时处理多个子节点的事件。</p>
<p>主要的作用有：1、省掉监听数，节省内存；要监听多个兄弟元素时，不如只监听父元素，并在事件处理函数中，利用e.target来判断到底是哪一个子元素触发了事件，再进行对应的处理即可。</p>
<p>2、监听不存在的元素，即动态元素。</p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2020/10/08/%E7%AE%97%E6%B3%95/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2020/10/08/%E7%AE%97%E6%B3%95/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/" class="post-title-link" itemprop="url">数据结构算法</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2020-10-08 19:42:38" itemprop="dateCreated datePublished" datetime="2020-10-08T19:42:38+08:00">2020-10-08</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:29:55" itemprop="dateModified" datetime="2021-11-03T16:29:55+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E7%AE%97%E6%B3%95/" itemprop="url" rel="index"><span itemprop="name">算法</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="二叉搜索树的操作集锦"><a href="#二叉搜索树的操作集锦" class="headerlink" title="二叉搜索树的操作集锦"></a>二叉搜索树的操作集锦</h1><h2 id="总路线"><a href="#总路线" class="headerlink" title="总路线"></a>总路线</h2><p>⼆叉树算法的设计的总路线：明确⼀个节点要做的事情，然后剩下的事抛给 框架。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">traverse</span><span class="params">(TreeNode root)</span> </span>&#123; </span><br><span class="line">    <span class="comment">// root 需要做什么？在这做。 </span></span><br><span class="line">    <span class="comment">// 其他的不⽤ root 操⼼，抛给框架 </span></span><br><span class="line">    traverse(root.left); </span><br><span class="line">    traverse(root.right); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="如何把⼆叉树所有的节点中的值加⼀？"><a href="#如何把⼆叉树所有的节点中的值加⼀？" class="headerlink" title="如何把⼆叉树所有的节点中的值加⼀？"></a><strong>如何把⼆叉树所有的节点中的值加⼀？</strong></h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">plusOne</span><span class="params">(TreeNode root)</span> </span>&#123; </span><br><span class="line">    <span class="keyword">if</span> (root == null) <span class="keyword">return</span>; </span><br><span class="line">    root.val += <span class="number">1</span>; </span><br><span class="line">    plusOne(root.left); </span><br><span class="line">    plusOne(root.right); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="如何判断两棵⼆叉树是否完全相同？"><a href="#如何判断两棵⼆叉树是否完全相同？" class="headerlink" title="如何判断两棵⼆叉树是否完全相同？"></a><strong>如何判断两棵⼆叉树是否完全相同？</strong></h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">isSameTree</span><span class="params">(TreeNode root1, TreeNode root2)</span> </span>&#123; </span><br><span class="line">    <span class="comment">// 都为空的话，显然相同 </span></span><br><span class="line">    <span class="keyword">if</span> (root1 == null &amp;&amp; root2 == null) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    <span class="comment">// ⼀个为空，⼀个⾮空，显然不同 </span></span><br><span class="line">    <span class="keyword">if</span> (root1 == null || root2 == null) <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">    <span class="comment">// 两个都⾮空，但 val 不⼀样也不⾏ </span></span><br><span class="line">    <span class="keyword">if</span> (root1.val != root2.val) <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">    <span class="comment">// root1 和 root2 该⽐的都⽐完了 </span></span><br><span class="line">    <span class="keyword">return</span> isSameTree(root1.left, root2.left) </span><br><span class="line">        &amp;&amp; isSameTree(root1.right, root2.right); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>借助框架可以理解上述两个例子，那么就能解决所有二叉树算法。</p>
<p>二叉搜索树BST是一种很常用的二叉树，定义为：一个二叉树中，任意节点的值要大于等于左子树所有节点的值，且要小于等于右边子树所有节点的值。</p>
<p>基础操作有：判断合法性、增、删、查。</p>
<h2 id="判断BST的合法性"><a href="#判断BST的合法性" class="headerlink" title="判断BST的合法性"></a>判断BST的合法性</h2><p>这⾥是有坑的哦，我们按照刚才的思路，每个节点⾃⼰要做的事不就是⽐较 ⾃⼰和左右孩⼦吗？看起来应该这样写代码：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">isValidBST</span><span class="params">(TreeNode root)</span> </span>&#123; </span><br><span class="line">    <span class="keyword">if</span> (root == null) <span class="keyword">return</span> <span class="literal">true</span>; </span><br><span class="line">    <span class="keyword">if</span> (root.left != null &amp;&amp; root.val &lt;= root.left.val) <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">    <span class="keyword">if</span> (root.right != null &amp;&amp; root.val &gt;= root.right.val) <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">    <span class="keyword">return</span> isValidBST(root.left) </span><br><span class="line">        &amp;&amp; isValidBST(root.right); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>但是这个算法出现了错误，BST 的每个节点应该要⼩于右边⼦树的所有节 点，下⾯这个⼆叉树显然不是 BST，但是我们的算法会把它判定为 BST。出现错误，不要慌张，框架没有错，⼀定是某个细节问题没注意到。我们重 新看⼀下 BST 的定义，root 需要做的不只是和左右⼦节点⽐较，⽽是要整 个左⼦树和右⼦树所有节点⽐较。怎么办，鞭⻓莫及啊！</p>
<p>这种情况，我们可以使⽤辅助函数，增加函数参数列表，在参数中携带额外 信息，请看正确的代码：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">isValidBST</span><span class="params">(TreeNode root)</span> </span>&#123; </span><br><span class="line">    <span class="keyword">return</span> isValidBST(root, null, null); </span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">isValidBST</span><span class="params">(TreeNode root, TreeNode <span class="built_in">min</span>, TreeNode <span class="built_in">max</span>)</span> </span>&#123; </span><br><span class="line">    <span class="keyword">if</span> (root == null) <span class="keyword">return</span> <span class="literal">true</span>; </span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">min</span> != null &amp;&amp; root.val &lt;= <span class="built_in">min</span>.val) <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">max</span> != null &amp;&amp; root.val &gt;= <span class="built_in">max</span>.val) <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">    <span class="keyword">return</span> isValidBST(root.left, <span class="built_in">min</span>, root) </span><br><span class="line">        &amp;&amp; isValidBST(root.right, root, <span class="built_in">max</span>); &#125;</span><br></pre></td></tr></table></figure>

<h2 id="在BST中查找一个数是否存在"><a href="#在BST中查找一个数是否存在" class="headerlink" title="在BST中查找一个数是否存在"></a>在BST中查找一个数是否存在</h2><p>根据我们的指导思想，可以这样写代码： </p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">isInBST</span><span class="params">(TreeNode root, <span class="keyword">int</span> target)</span> </span>&#123; </span><br><span class="line">    <span class="keyword">if</span> (root == null) <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">    <span class="keyword">if</span> (root.val == target) <span class="keyword">return</span> <span class="literal">true</span>; </span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> isInBST(root.left, target) || </span><br><span class="line">        isInBST(root.right, target); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这样写完全正确，充分证明了你的框架性思维已经养成。现在你可以考虑⼀ 点细节问题了：如何充分利⽤信息，把 BST 这个“左⼩右⼤”的特性⽤上？ 很简单，其实不需要递归地搜索两边，类似⼆分查找思想，根据 target 和 root.val 的⼤⼩⽐较，就能排除⼀边。我们把上⾯的思路稍稍改动：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">isInBST</span><span class="params">(TreeNode root, <span class="keyword">int</span> target)</span> </span>&#123; </span><br><span class="line">    <span class="keyword">if</span> (root == null) <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">    <span class="keyword">if</span> (root.val == target) </span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>; </span><br><span class="line">    <span class="keyword">if</span> (root.val &lt; target) </span><br><span class="line">        <span class="keyword">return</span> isInBST(root.right, target);</span><br><span class="line">    <span class="keyword">if</span> (root.val &gt; target) </span><br><span class="line">        <span class="keyword">return</span> isInBST(root.left, target); </span><br><span class="line">    <span class="comment">// root 该做的事做完了，顺带把框架也完成了，妙 </span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>于是，我们对原始框架进⾏改造，抽象出⼀套<strong>针对</strong> <strong>BST</strong> <strong>的遍历框架</strong>：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">BST</span><span class="params">(TreeNode root, <span class="keyword">int</span> target)</span> </span>&#123; </span><br><span class="line">    <span class="keyword">if</span> (root.val == target) </span><br><span class="line">        <span class="comment">// 找到⽬标，做点什么 </span></span><br><span class="line">    <span class="keyword">if</span> (root.val &lt; target) </span><br><span class="line">            BST(root.right, target);</span><br><span class="line">    <span class="keyword">if</span> (root.val &gt; target) </span><br><span class="line">            BST(root.left, target); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="在-BST-中插⼊⼀个数"><a href="#在-BST-中插⼊⼀个数" class="headerlink" title="在 BST 中插⼊⼀个数"></a><strong>在</strong> <strong>BST</strong> <strong>中插⼊⼀个数</strong></h2><p>对数据结构的操作⽆⾮遍历 + 访问，遍历就是“找”，访问就是“改”。具体到 这个问题，插⼊⼀个数，就是先找到插⼊位置，然后进⾏插⼊操作。 </p>
<p>上⼀个问题，我们总结了 BST 中的遍历框架，就是“找”的问题。直接套框 架，加上“改”的操作即可。⼀旦涉及“改”，函数就要返回 TreeNode 类型， 并且对递归调⽤的返回值进⾏接收。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">TreeNode <span class="title">insertIntoBST</span><span class="params">(TreeNode root, <span class="keyword">int</span> val)</span> </span>&#123; </span><br><span class="line">    <span class="comment">// 找到空位置插⼊新节点 </span></span><br><span class="line">    <span class="keyword">if</span> (root == null) </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> TreeNode(val); </span><br><span class="line">    <span class="comment">// if (root.val == val) </span></span><br><span class="line">    <span class="comment">// BST 中⼀般不会插⼊已存在元素 </span></span><br><span class="line">    <span class="keyword">if</span> (root.val &lt; val) </span><br><span class="line">        root.right = insertIntoBST(root.right, val); </span><br><span class="line">    <span class="keyword">if</span> (root.val &gt; val) </span><br><span class="line">        root.left = insertIntoBST(root.left, val); </span><br><span class="line">    <span class="keyword">return</span> root; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="在-BST-中删除⼀个数"><a href="#在-BST-中删除⼀个数" class="headerlink" title="在 BST 中删除⼀个数"></a><strong>在</strong> <strong>BST</strong> <strong>中删除⼀个数</strong></h2><p>这个问题稍微复杂，不过你有框架指导，难不住你。跟插⼊操作类似， 先“找”再“改”，先把框架写出来再说： </p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">TreeNode <span class="title">deleteNode</span><span class="params">(TreeNode root, <span class="keyword">int</span> key)</span> </span>&#123; </span><br><span class="line">    <span class="keyword">if</span> (root.val == key) &#123; </span><br><span class="line">        <span class="comment">// 找到啦，进⾏删除 </span></span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (root.val &gt; key) &#123; </span><br><span class="line">        root.left = deleteNode(root.left, key); </span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (root.val &lt; key) &#123; </span><br><span class="line">        root.right = deleteNode(root.right, key); </span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> root; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>找到⽬标节点了，⽐⽅说是节点 A，如何删除这个节点，这是难点。因为删 除节点的同时不能破坏 BST 的性质。有三种情况，⽤图⽚来说明。 </p>
<p>情况 1：A 恰好是末端节点，两个⼦节点都为空，那么它可以当场去世了。 </p>
<p>情况 2：A 只有⼀个⾮空⼦节点，那么它要让这个孩⼦接替⾃⼰的位置。</p>
<p>情况 3：A 有两个⼦节点，⿇烦了，为了不破坏 BST 的性质，A 必须找到 左⼦树中最⼤的那个节点，或者右⼦树中最⼩的那个节点来接替⾃⼰。我们 以第⼆种⽅式讲解。 </p>
<p>三种情况分析完毕，填⼊框架，简化⼀下代码：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">TreeNode <span class="title">deleteNode</span><span class="params">(TreeNode root, <span class="keyword">int</span> key)</span> </span>&#123; </span><br><span class="line">    <span class="keyword">if</span> (root == null) <span class="keyword">return</span> null; </span><br><span class="line">    <span class="keyword">if</span> (root.val == key) &#123; </span><br><span class="line">        <span class="comment">// 这两个 if 把情况 1 和 2 都正确处理了 </span></span><br><span class="line">        <span class="keyword">if</span> (root.left == null) <span class="keyword">return</span> root.right; </span><br><span class="line">        <span class="keyword">if</span> (root.right == null) <span class="keyword">return</span> root.left; </span><br><span class="line">        <span class="comment">// 处理情况 3 </span></span><br><span class="line">        TreeNode minNode = getMin(root.right); </span><br><span class="line">        root.val = minNode.val; </span><br><span class="line">        root.right = deleteNode(root.right, minNode.val); </span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (root.val &gt; key) &#123; </span><br><span class="line">        root.left = deleteNode(root.left, key); </span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (root.val &lt; key) &#123; </span><br><span class="line">        root.right = deleteNode(root.right, key); </span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> root; </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">TreeNode <span class="title">getMin</span><span class="params">(TreeNode node)</span> </span>&#123; </span><br><span class="line">    <span class="comment">// BST 最左边的就是最⼩的 </span></span><br><span class="line">    <span class="keyword">while</span> (node.left != null) node = node.left; </span><br><span class="line">    <span class="keyword">return</span> node; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>删除操作就完成了。注意⼀下，这个删除操作并不完美，因为我们⼀般不会 通过 root.val = minNode.val 修改节点内部的值来交换节点，⽽是通过⼀系列 略微复杂的链表操作交换 root 和 minNode 两个节点。因为具体应⽤中，val 域可能会很⼤，修改起来很耗时，⽽链表操作⽆⾮改⼀改指针，⽽不会去碰 内部数据。 </p>
<p>但这⾥忽略这个细节，旨在突出 BST 基本操作的共性，以及借助框架逐层 细化问题的思维⽅式。 </p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>通过这篇⽂章，你学会了如下⼏个技巧： </p>
<p>1、⼆叉树算法设计的总路线：把当前节点要做的事做好，其他的交给递归 </p>
<p>框架，不⽤当前节点操⼼。 </p>
<p>2、如果当前节点会对下⾯的⼦节点有整体影响，可以通过辅助函数增⻓参 </p>
<p>数列表，借助参数传递信息。 </p>
<p>3、在⼆叉树框架之上，扩展出⼀套 BST 遍历框架：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">BST</span><span class="params">(TreeNode root, <span class="keyword">int</span> target)</span> </span>&#123; </span><br><span class="line">    <span class="keyword">if</span> (root.val == target) ;</span><br><span class="line">        <span class="comment">// 找到⽬标，做点什么 </span></span><br><span class="line">    <span class="keyword">if</span> (root.val &lt; target) </span><br><span class="line">            BST(root.right, target); </span><br><span class="line">    <span class="keyword">if</span> (root.val &gt; target) </span><br><span class="line">        BST(root.left, target); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>4、掌握了 BST 的基本操作。</p>
<h1 id="快速计算完全⼆叉树的节点"><a href="#快速计算完全⼆叉树的节点" class="headerlink" title="快速计算完全⼆叉树的节点"></a>快速计算完全⼆叉树的节点</h1><p>如果让你数⼀下⼀棵普通⼆叉树有多少个节点，这很简单，只要在⼆叉树的 遍历框架上加⼀点代码就⾏了。 </p>
<p>但是，如果给你⼀棵完全⼆叉树，让你计算它的节点个数，你会不会？算法 的时间复杂度是多少？这个算法的时间复杂度应该是 O(logN*logN)，如果 你⼼中的算法没有达到⾼效，那么本⽂就是给你写的。 </p>
<p>⾸先要明确⼀下两个关于⼆叉树的名词「完全⼆叉树」和「满⼆叉树」。 我们说的<strong>完全⼆叉树</strong>如下图，每⼀层都是紧凑靠左排列的： 我们说的<strong>满⼆叉树</strong>如下图，是⼀种特殊的完全⼆叉树，每层都是是满的，像 ⼀个稳定的三⾓形： </p>
<h2 id="具体方法"><a href="#具体方法" class="headerlink" title="具体方法"></a>具体方法</h2><p>如果是一个<strong>普通</strong>二叉树，显然只要向下面这样遍历一边即可，时间复杂度 O(N)：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">countNodes</span><span class="params">(TreeNode root)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (root == null) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span> + countNodes(root.left) + countNodes(root.right);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>那如果是一棵<strong>满</strong>二叉树，节点总数就和树的高度呈指数关系，时间复杂度 O(logN)：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">countNodes</span><span class="params">(TreeNode root)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> h = <span class="number">0</span>;</span><br><span class="line">    <span class="comment">// 计算树的高度</span></span><br><span class="line">    <span class="keyword">while</span> (root != null) &#123;</span><br><span class="line">        root = root.left;</span><br><span class="line">        h++;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 节点总数就是 2^h - 1</span></span><br><span class="line">    <span class="keyword">return</span> (<span class="keyword">int</span>)Math.<span class="built_in">pow</span>(<span class="number">2</span>, h) - <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>完全</strong>二叉树比普通二叉树特殊，但又没有满二叉树那么特殊，计算它的节点总数，可以说是普通二叉树和完全二叉树的结合版，先看代码：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">countNodes</span><span class="params">(TreeNode root)</span> </span>&#123;</span><br><span class="line">    TreeNode l = root, r = root;</span><br><span class="line">    <span class="comment">// 记录左、右子树的高度</span></span><br><span class="line">    <span class="keyword">int</span> hl = <span class="number">0</span>, hr = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span> (l != null) &#123;</span><br><span class="line">        l = l.left;</span><br><span class="line">        hl++;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">while</span> (r != null) &#123;</span><br><span class="line">        r = r.right;</span><br><span class="line">        hr++;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果左右子树的高度相同，则是一棵满二叉树</span></span><br><span class="line">    <span class="keyword">if</span> (hl == hr) &#123;</span><br><span class="line">        <span class="keyword">return</span> (<span class="keyword">int</span>)Math.<span class="built_in">pow</span>(<span class="number">2</span>, hl) - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果左右高度不同，则按照普通二叉树的逻辑计算</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span> + countNodes(root.left) + countNodes(root.right);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>结合刚才针对满二叉树和普通二叉树的算法，上面这段代码应该不难理解，就是一个结合版，但是<strong>其中降低时间复杂度的技巧是非常微妙的</strong>。</p>
<h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><p>开头说了，这个算法的时间复杂度是 O(logN<em>logN)，这是怎么算出来的呢？直觉感觉好像最坏情况下是 O(N</em>logN) 吧，因为之前的 while 需要 logN 的时间，最后要 O(N) 的时间向左右子树递归：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">return</span> <span class="number">1</span> + countNodes(root.left) + countNodes(root.right);</span><br></pre></td></tr></table></figure>

<p><strong>关键点在于，这两个递归只有一个会真的递归下去，另一个一定会触发<code>hl == hr</code>而立即返回，不会递归下去</strong>。</p>
<p>为什么呢？原因如下：</p>
<p><strong>一棵完全二叉树的两棵子树，至少有一棵是满二叉树</strong>：</p>
<p>看图就明显了吧，由于完全二叉树的性质，其子树一定有一棵是满的，所以一定会触发<code>hl == hr</code>，只消耗 O(logN) 的复杂度而不会继续递归。</p>
<p>综上，算法的递归深度就是树的高度 O(logN)，每次递归所花费的时间就是 while 循环，需要 O(logN)，所以总体的时间复杂度是 O(logN*logN)。所以说，「完全二叉树」这个概念还是有它存在的原因的，不仅适用于数组实现二叉堆，而且连计算节点总数这种看起来简单的操作都有高效的算法实现。</p>
<h1 id="如何使用单调栈解题"><a href="#如何使用单调栈解题" class="headerlink" title="如何使用单调栈解题"></a>如何使用单调栈解题</h1><p>单调栈实际上就是栈，只是利⽤了⼀些巧妙的逻辑，使得每次新元素⼊栈 后，栈内的元素都保持有序（单调递增或单调递减）。听起来有点像堆（heap）？不是的，单调栈⽤途不太⼴泛，只处理⼀种典型 的问题，叫做 Next Greater Element。本⽂⽤讲解单调队列的算法模版解决 这类问题，并且探讨处理「循环数组」的策略。 </p>
<h2 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h2><p>⾸先，讲解 Next Greater Number 的原始问题：给你⼀个数组，返回⼀个等 ⻓的数组，对应索引存储着下⼀个更⼤元素，如果没有更⼤的元素，就存 -1。不好⽤语⾔解释清楚，直接上⼀个例⼦： </p>
<p>给你⼀个数组 [2,1,2,4,3]，你返回数组 [4,2,4,-1,-1]。 </p>
<p>解释：第⼀个 2 后⾯⽐ 2 ⼤的数是 4; 1 后⾯⽐ 1 ⼤的数是 2；第⼆个 2 后⾯ ⽐ 2 ⼤的数是 4; 4 后⾯没有⽐ 4 ⼤的数，填 -1；3 后⾯没有⽐ 3 ⼤的数，填 -1。这道题的暴⼒解法很好想到，就是对每个元素后⾯都进⾏扫描，找到第⼀个 更⼤的元素就⾏了。但是暴⼒解法的时间复杂度是 O(n^2)。 </p>
<h2 id="解法"><a href="#解法" class="headerlink" title="解法"></a>解法</h2><p>这个问题可以这样抽象思考：把数组的元素想象成并列站⽴的⼈，元素⼤⼩ 想象成⼈的⾝⾼。这些⼈⾯对你站成⼀列，如何求元素「2」的 Next Greater Number 呢？很简单，如果能够看到元素「2」，那么他后⾯可⻅的第⼀个 ⼈就是「2」的 Next Greater Number，因为⽐「2」⼩的元素⾝⾼不够，都被「2」挡住了，第⼀个露出来的就是答案。 </p>
<p>这个情景很好理解吧？带着这个抽象的情景，先来看下代码</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; <span class="title">nextGreaterElement</span><span class="params">(<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&amp; nums)</span> </span>&#123; </span><br><span class="line">    <span class="function"><span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; <span class="title">ans</span><span class="params">(nums.<span class="built_in">size</span>())</span></span>; </span><br><span class="line">    <span class="comment">// 存放答案的数组 </span></span><br><span class="line">    <span class="built_in">stack</span>&lt;<span class="keyword">int</span>&gt; s; </span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = nums.<span class="built_in">size</span>() - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123; </span><br><span class="line">        <span class="comment">// 倒着往栈⾥放 </span></span><br><span class="line">        <span class="keyword">while</span> (!s.empty() &amp;&amp; s.top() &lt;= nums[i]) &#123; </span><br><span class="line">            <span class="comment">// 判定个⼦⾼矮 </span></span><br><span class="line">            s.pop(); <span class="comment">// 矮个起开，反正也被挡着了。。。 </span></span><br><span class="line">        &#125;</span><br><span class="line">        ans[i] = s.empty() ? <span class="number">-1</span> : s.top(); <span class="comment">// 这个元素⾝后的第⼀个⾼个 </span></span><br><span class="line">        s.push(nums[i]); </span><br><span class="line">        <span class="comment">// 进队，接受之后的⾝⾼判定吧！ </span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ans; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这就是单调队列解决问题的模板。for 循环要从后往前扫描元素，因为我们 借助的是栈的结构，倒着⼊栈，其实是正着出栈。while 循环是把两个“⾼ 个”元素之间的元素排除，因为他们的存在没有意义，前⾯挡着个“更⾼”的 元素，所以他们不可能被作为后续进来的元素的 Next Great Number 了。 这个算法的时间复杂度不是那么直观，如果你看到 for 循环嵌套 while 循 环，可能认为这个算法的复杂度也是 O(n^2)，但是实际上这个算法的复杂 度只有 O(n)</p>
<h2 id="时间复杂度"><a href="#时间复杂度" class="headerlink" title="时间复杂度"></a>时间复杂度</h2><p>分析它的时间复杂度，要从整体来看：总共有 n 个元素，每个元素都被 push ⼊栈了⼀次，⽽最多会被 pop ⼀次，没有任何冗余操作。所以总的计 算规模是和元素规模 n 成正⽐的，也就是 O(n) 的复杂度。 </p>
<p>给你⼀个数组 T = [73, 74, 75, 71, 69, 72, 76, 73]，这个数组存放的是近⼏天 的天⽓⽓温（这⽓温是铁板烧？不是的，这⾥⽤的华⽒度）。你返回⼀个数 组，计算：对于每⼀天，你还要⾄少等多少天才能等到⼀个更暖和的⽓温； 如果等不到那⼀天，填 0 。</p>
<p>举例：给你 T = [73, 74, 75, 71, 69, 72, 76, 73]，你返回 [1, 1, 4, 2, 1, 1, 0, 0]。 </p>
<p>解释：第⼀天 73 华⽒度，第⼆天 74 华⽒度，⽐ 73 ⼤，所以对于第⼀天， 只要等⼀天就能等到⼀个更暖和的⽓温。后⾯的同理。 你已经对 Next Greater Number 类型问题有些敏感了，这个问题本质上也是 找 Next Greater Number，只不过现在不是问你 Next Greater Number 是多 少，⽽是问你当前距离 Next Greater Number 的距离⽽已。 相同类型的问题，相同的思路，直接调⽤单调栈的算法模板，稍作改动就可 以啦，直接上代码把</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; <span class="title">dailyTemperatures</span><span class="params">(<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&amp; T)</span> </span>&#123; </span><br><span class="line">    <span class="function"><span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; <span class="title">ans</span><span class="params">(T.<span class="built_in">size</span>())</span></span>; </span><br><span class="line">    <span class="built_in">stack</span>&lt;<span class="keyword">int</span>&gt; s; <span class="comment">// 这⾥放元素索引，⽽不是元素 </span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = T.<span class="built_in">size</span>() - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123; </span><br><span class="line">        <span class="keyword">while</span> (!s.empty() &amp;&amp; T[s.top()] &lt;= T[i]) &#123; </span><br><span class="line">            s.pop(); </span><br><span class="line">        &#125;</span><br><span class="line">        ans[i] = s.empty() ? <span class="number">0</span> : (s.top() - i); </span><br><span class="line">        <span class="comment">// 得到索引间距 </span></span><br><span class="line">        s.push(i); </span><br><span class="line">        <span class="comment">// 加⼊索引，⽽不是元素 </span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ans; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="扩展问题：循环数组"><a href="#扩展问题：循环数组" class="headerlink" title="扩展问题：循环数组"></a>扩展问题：循环数组</h2><p>单调栈讲解完毕。下⾯开始另⼀个重点：如何处理「循环数组」。 </p>
<p>同样是 Next Greater Number，现在假设给你的数组是个环形的，如何处理？ </p>
<p>给你⼀个数组 [2,1,2,4,3]，你返回数组 [4,2,4,-1,4]。拥有了环形属性，最后 ⼀个元素 3 绕了⼀圈后找到了⽐⾃⼰⼤的元素 4 。 </p>
<p>⾸先，计算机的内存都是线性的，没有真正意义上的环形数组，但是我们可 以模拟出环形数组的效果，⼀般是通过 % 运算符求模（余数），获得环形特效</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span>[] arr = &#123;<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>&#125;; </span><br><span class="line"><span class="keyword">int</span> n = arr.length, index = <span class="number">0</span>; </span><br><span class="line"><span class="keyword">while</span> (<span class="literal">true</span>) &#123; </span><br><span class="line">    <span class="built_in">print</span>(arr[index % n]); </span><br><span class="line">    index++; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>回到 Next Greater Number 的问题，增加了环形属性后，问题的难点在于： </p>
<p>这个 Next 的意义不仅仅是当前元素的右边了，有可能出现在当前元素的左 边（如上例）。 </p>
<p>明确问题，问题就已经解决了⼀半了。我们可以考虑这样的思路：将原始数 组“翻倍”，就是在后⾯再接⼀个原始数组，这样的话，按照之前“⽐⾝⾼”的 流程，每个元素不仅可以⽐较⾃⼰右边的元素，⽽且也可以和左边的元素⽐ 较了</p>
<p>怎么实现呢？你当然可以把这个双倍⻓度的数组构造出来，然后套⽤算法模 板。但是，我们可以不⽤构造新数组，⽽是利⽤循环数组的技巧来模拟。直 接看代码吧： </p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; <span class="title">nextGreaterElements</span><span class="params">(<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;&amp; nums)</span> </span>&#123; </span><br><span class="line">    <span class="keyword">int</span> n = nums.<span class="built_in">size</span>(); </span><br><span class="line">    <span class="function"><span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; <span class="title">res</span><span class="params">(n)</span></span>; </span><br><span class="line">    <span class="comment">// 存放结果 </span></span><br><span class="line">    <span class="built_in">stack</span>&lt;<span class="keyword">int</span>&gt; s; </span><br><span class="line">    <span class="comment">// 假装这个数组⻓度翻倍了 </span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">2</span> * n - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123; </span><br><span class="line">        <span class="keyword">while</span> (!s.empty() &amp;&amp; s.top() &lt;= nums[i % n]) </span><br><span class="line">            s.pop(); </span><br><span class="line">        res[i % n] = s.empty() ? <span class="number">-1</span> : s.top(); </span><br><span class="line">        s.push(nums[i % n]); </span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="利用单调队列解题"><a href="#利用单调队列解题" class="headerlink" title="利用单调队列解题"></a>利用单调队列解题</h1><p>前⽂讲了⼀种特殊的数据结构「单调栈」monotonic stack，解决了⼀类问题 「Next Greater Number」，本⽂写⼀个类似的数据结构「单调队列」。 也许这种数据结构的名字你没听过，其实没啥难的，就是⼀个「队列」，只 是使⽤了⼀点巧妙的⽅法，使得队列中的元素单调递增（或递减）。这个数 据结构有什么⽤？可以解决滑动窗⼝的⼀系列问题</p>
<h2 id="题目-1"><a href="#题目-1" class="headerlink" title="题目"></a>题目</h2><p>给定一个数组nums，有一个大小为k的滑动窗口从数组的最左侧移动到数组的最右侧；你只可以看到在滑动窗口k内的数字，滑动窗口每次只向右移动一位，返回滑动窗口最大值。</p>
<h1 id="二叉堆详解"><a href="#二叉堆详解" class="headerlink" title="二叉堆详解"></a>二叉堆详解</h1><p>⼆叉堆（Binary Heap）没什么神秘，性质⽐⼆叉搜索树 BST 还简单。其主 要操作就两个， sink （下沉）和 swim （上浮），⽤以维护⼆叉堆的性 质。其主要应⽤有两个，⾸先是⼀种排序⽅法「堆排序」，第⼆是⼀种很有 ⽤的数据结构「优先级队列」。 </p>
<p>本⽂就以实现优先级队列（Priority Queue）为例，通过图⽚和⼈类的语⾔来 描述⼀下⼆叉堆怎么运作的</p>
<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>⼆叉堆其实就是⼀种特殊的⼆叉树（完全⼆叉树），只不过存储在数 组⾥。⼀般的链表⼆叉树，我们操作节点的指针，⽽在数组⾥，我们把数组 索引作为指针： </p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ⽗节点的索引 </span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">parent</span><span class="params">(<span class="keyword">int</span> root)</span> </span>&#123; </span><br><span class="line">    <span class="keyword">return</span> root / <span class="number">2</span>; </span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 左孩⼦的索引 </span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">left</span><span class="params">(<span class="keyword">int</span> root)</span> </span>&#123; </span><br><span class="line">    <span class="keyword">return</span> root * <span class="number">2</span>; </span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 右孩⼦的索引 </span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">right</span><span class="params">(<span class="keyword">int</span> root)</span> </span>&#123; </span><br><span class="line">    <span class="keyword">return</span> root * <span class="number">2</span> + <span class="number">1</span>; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>画个图你⽴即就能理解了，注意数组的第⼀个索引 0 空着不⽤</p>
<p><img src="/../../image/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/%E4%BA%8C%E5%8F%89%E5%A0%86.png" alt="二叉堆"></p>
<p>PS：因为数组索引是数组，为了⽅便区分，将字符作为数组元素。 </p>
<p>你看到了，把 arr[1] 作为整棵树的根的话，每个节点的⽗节点和左右孩⼦的 索引都可以通过简单的运算得到，这就是⼆叉堆设计的⼀个巧妙之处。为了 ⽅便讲解，下⾯都会画的图都是⼆叉树结构，相信你能把树和数组对应起 来</p>
<p>⼆叉堆还分为最⼤堆和最⼩堆。<strong>最⼤堆的性质是：每个节点都⼤于等于它的</strong> <strong>两个⼦节点。</strong>类似的，最⼩堆的性质是：每个节点都⼩于等于它的⼦节点。 两种堆核⼼思路都是⼀样的，本⽂以最⼤堆为例讲解。 对于⼀个最⼤堆，根据其性质，显然堆顶，也就是 arr[1] ⼀定是所有元素中 最⼤的元素。 </p>
<h2 id="优先级队列"><a href="#优先级队列" class="headerlink" title="优先级队列"></a>优先级队列</h2><p>优先级队列这种数据结构有⼀个很有⽤的功能，你插⼊或者删除元素的时 候，元素会⾃动排序，这底层的原理就是⼆叉堆的操作。 </p>
<p>数据结构的功能⽆⾮增删查该，优先级队列有两个主要 API，分别是 insert 插⼊⼀个元素和 delMax 删除最⼤元素（如果底层⽤最⼩堆，那么 就是 delMin ）。 </p>
<p>下⾯我们实现⼀个简化的优先级队列，先看下代码框架： PS：为了清晰起⻅，这⾥⽤到 Java 的泛型， Key 可以是任何⼀种可⽐较⼤ ⼩的数据类型，你可以认为它是 int、char 等。 </p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MaxPQ</span> </span></span><br><span class="line"><span class="class">    &lt;<span class="title">Key</span> <span class="keyword">extends</span> <span class="title">Comparable</span>&lt;<span class="title">Key</span>&gt;&gt; </span>&#123; </span><br><span class="line">    <span class="comment">// 存储元素的数组 </span></span><br><span class="line">    <span class="keyword">private</span> Key[] pq; </span><br><span class="line">    <span class="comment">// 当前 Priority Queue 中的元素个数 </span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> N = <span class="number">0</span>; <span class="function"><span class="keyword">public</span> <span class="title">MaxPQ</span><span class="params">(<span class="keyword">int</span> cap)</span> </span>&#123; </span><br><span class="line">        <span class="comment">// 索引 0 不⽤，所以多分配⼀个空间 </span></span><br><span class="line">        pq = (Key[]) <span class="keyword">new</span> Comparable[cap + <span class="number">1</span>]; </span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* 返回当前队列中最⼤元素 */</span> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> Key <span class="title">max</span><span class="params">()</span> </span>&#123; </span><br><span class="line">        <span class="keyword">return</span> pq[<span class="number">1</span>]; </span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">/* 插⼊元素 e */</span> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">insert</span><span class="params">(Key e)</span> </span>&#123;...&#125; </span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* 删除并返回当前队列中最⼤元素 */</span> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> Key <span class="title">delMax</span><span class="params">()</span> </span>&#123;...&#125; </span><br><span class="line">   </span><br><span class="line">    <span class="comment">/* 上浮第 k 个元素，以维护最⼤堆性质 */</span> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">swim</span><span class="params">(<span class="keyword">int</span> k)</span> </span>&#123;...&#125; </span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* 下沉第 k 个元素，以维护最⼤堆性质 */</span> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">sink</span><span class="params">(<span class="keyword">int</span> k)</span> </span>&#123;...&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* 交换数组的两个元素 */</span> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">exch</span><span class="params">(<span class="keyword">int</span> i, <span class="keyword">int</span> j)</span> </span>&#123; </span><br><span class="line">        Key temp = pq[i]; </span><br><span class="line">        pq[i] = pq[j]; </span><br><span class="line">        pq[j] = temp; </span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* pq[i] 是否⽐ pq[j] ⼩？ */</span> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">less</span><span class="params">(<span class="keyword">int</span> i, <span class="keyword">int</span> j)</span> </span>&#123; </span><br><span class="line">        <span class="keyword">return</span> pq[i].compareTo(pq[j]) &lt; <span class="number">0</span>; </span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">/* 还有 left, right, parent 三个⽅法 */</span> </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>空出来的四个⽅法是⼆叉堆和优先级队列的奥妙所在，下⾯⽤图⽂来逐个理解</p>
<h2 id="swim和sink"><a href="#swim和sink" class="headerlink" title="swim和sink"></a>swim和sink</h2><p>为什么要有上浮 swim 和下沉 sink 的操作呢？为了维护堆结构。 我们要讲的是最⼤堆，每个节点都⽐它的两个⼦节点⼤，但是在插⼊元素和 删除元素时，难免破坏堆的性质，这就需要通过这两个操作来恢复堆的性质了。</p>
<p>对于最⼤堆，会破坏堆性质的有有两种情况： </p>
<p>1、 如果某个节点 A ⽐它的⼦节点（中的⼀个）⼩，那么 A 就不配做⽗节点，应该下去，下⾯那个更⼤的节点上来做⽗节点，这就是对 A 进⾏ <strong>下沉</strong>。 </p>
<p>2、 如果某个节点 A ⽐它的⽗节点⼤，那么 A 不应该做⼦节点，应该把⽗ 节点换下来，⾃⼰去做⽗节点，这就是对 A 的<strong>上浮</strong>。 </p>
<p>当然，错位的节点 A 可能要上浮（或下沉）很多次，才能到达正确的位 置，恢复堆的性质。所以代码中肯定有⼀个 while 循环。 </p>
<p>细⼼的读者也许会问，这两个操作不是互逆吗，所以上浮的操作⼀定能⽤下 沉来完成，为什么我还要费劲写两个⽅法？ 是的，操作是互逆等价的，但是最终我们的操作只会在堆底和堆顶进⾏（等 会讲原因），显然堆底的「错位」元素需要上浮，堆顶的「错位」元素需要 下沉</p>
<p><strong>上浮的代码实现：</strong> </p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">swim</span><span class="params">(<span class="keyword">int</span> k)</span> </span>&#123; </span><br><span class="line">    <span class="comment">// 如果浮到堆顶，就不能再上浮了 </span></span><br><span class="line">    <span class="keyword">while</span> (k &gt; <span class="number">1</span> &amp;&amp; less(parent(k), k)) &#123; </span><br><span class="line">        <span class="comment">// 如果第 k 个元素⽐上层⼤ </span></span><br><span class="line">        <span class="comment">// 将 k 换上去 </span></span><br><span class="line">        exch(parent(k), k); </span><br><span class="line">        k = parent(k); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>下沉的代码实现：</strong> </p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">sink</span><span class="params">(<span class="keyword">int</span> k)</span> </span>&#123; </span><br><span class="line">    <span class="comment">// 如果沉到堆底，就沉不下去了 </span></span><br><span class="line">    <span class="keyword">while</span> (left(k) &lt;= N) &#123; </span><br><span class="line">        <span class="comment">// 先假设左边节点较⼤ </span></span><br><span class="line">        <span class="keyword">int</span> older = left(k); </span><br><span class="line">        <span class="comment">// 如果右边节点存在，⽐⼀下⼤⼩ </span></span><br><span class="line">        <span class="keyword">if</span> (right(k) &lt;= N &amp;&amp; less(older, right(k))) </span><br><span class="line">            older = right(k); </span><br><span class="line">        <span class="comment">// 结点 k ⽐俩孩⼦都⼤，就不必下沉了 </span></span><br><span class="line">        <span class="keyword">if</span> (less(older, k)) <span class="keyword">break</span>;</span><br><span class="line">        <span class="comment">// 否则，不符合最⼤堆的结构，下沉 k 结点 </span></span><br><span class="line">        exch(k, older); </span><br><span class="line">        k = older; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>⾄此，⼆叉堆的主要操作就讲完了，⼀点都不难吧，代码加起来也就⼗⾏。 明⽩了 sink 和 swim 的⾏为，下⾯就可以实现优先级队列了</p>
<h2 id="实现-delMax-和-insert"><a href="#实现-delMax-和-insert" class="headerlink" title="实现 delMax 和 insert"></a><strong>实现</strong> <strong>delMax</strong> <strong>和</strong> <strong>insert</strong></h2><p>这两个⽅法就是建⽴在 swim 和 sink 上的。 <strong>insert</strong> <strong>⽅法先把要插⼊的元素添加到堆底的最后，然后让其上浮到正确位</strong> <strong>置。</strong></p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">insert</span><span class="params">(Key e)</span> </span>&#123; </span><br><span class="line">    N++; </span><br><span class="line">    <span class="comment">// 先把新元素加到最后 </span></span><br><span class="line">    pq[N] = e; </span><br><span class="line">    <span class="comment">// 然后让它上浮到正确的位置 </span></span><br><span class="line">    swim(N); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>delMax</strong> <strong>⽅法先把堆顶元素</strong> <strong>A</strong> <strong>和堆底最后的元素</strong> <strong>B</strong> <strong>对调，然后删除</strong> <strong>A****，最</strong> <strong>后让</strong> <strong>B</strong> <strong>下沉到正确位置。</strong></p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> Key <span class="title">delMax</span><span class="params">()</span> </span>&#123; </span><br><span class="line">    <span class="comment">// 最⼤堆的堆顶就是最⼤元素 </span></span><br><span class="line">    Key <span class="built_in">max</span> = pq[<span class="number">1</span>]; </span><br><span class="line">    <span class="comment">// 把这个最⼤元素换到最后，删除之 </span></span><br><span class="line">    exch(<span class="number">1</span>, N);</span><br><span class="line">    pq[N] = null; </span><br><span class="line">    N--; </span><br><span class="line">    <span class="comment">// 让 pq[1] 下沉到正确位置 </span></span><br><span class="line">    sink(<span class="number">1</span>); </span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">max</span>; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>⾄此，⼀个优先级队列就实现了，插⼊和删除元素的时间复杂度为 O(logK) ， K 为当前⼆叉堆（优先级队列）中的元素总数。因为我们时间 复杂度主要花费在 sink 或者 swim 上，⽽不管上浮还是下沉，最多也就 树（堆）的⾼度，也就是 log 级别</p>
<h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>⼆叉堆就是⼀种完全⼆叉树，所以适合存储在数组中，⽽且⼆叉堆拥有⼀些 特殊性质。 </p>
<p>⼆叉堆的操作很简单，主要就是上浮和下沉，来维护堆的性质（堆有序）， 核⼼代码也就⼗⾏。 </p>
<p>优先级队列是基于⼆叉堆实现的，主要操作是插⼊和删除。插⼊是先插到最 后，然后上浮到正确位置；删除是调换位置后再删除，然后下沉到正确位 置。核⼼代码也就⼗⾏。 </p>
<p>也许这就是数据结构的威⼒，简单的操作就能实现巧妙的功能，真⼼佩服发明⼆叉堆算法的⼈！ </p>
<h1 id=""><a href="#" class="headerlink" title=""></a></h1>
      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  


  
  <nav class="pagination">
    <a class="extend prev" rel="prev" href="/page/6/"><i class="fa fa-angle-left" aria-label="上一页"></i></a><a class="page-number" href="/">1</a><span class="space">&hellip;</span><a class="page-number" href="/page/6/">6</a><span class="page-number current">7</span><a class="page-number" href="/page/8/">8</a><span class="space">&hellip;</span><a class="page-number" href="/page/10/">10</a><a class="extend next" rel="next" href="/page/8/"><i class="fa fa-angle-right" aria-label="下一页"></i></a>
  </nav>



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="Li Yudong"
      src="/images/author.jpg">
  <p class="site-author-name" itemprop="name">Li Yudong</p>
  <div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">95</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">14</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">18</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/tiarmor1" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;tiarmor1" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="mailto:1157019137@qq.com" title="E-Mail → mailto:1157019137@qq.com" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>E-Mail</a>
      </span>
  </div>
  <div class="cc-license motion-element" itemprop="license">
    <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" class="cc-opacity" rel="noopener" target="_blank"><img src="/images/cc-by-nc-sa.svg" alt="Creative Commons"></a>
  </div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 2020 – 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">Li Yudong</span>
</div>

        








      </div>
    </footer>
  </div>

  
  
  <script color='0,0,0' opacity='0.5' zIndex='-1' count='150' src="/lib1/canvas-nest/canvas-nest.min.js"></script>
  <script src="/lib1/anime.min.js"></script>
  <script src="/lib1/velocity/velocity.min.js"></script>
  <script src="/lib1/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/pisces.js"></script>


<script src="/js/next-boot.js"></script>




  




  
<script src="/js/local-search.js"></script>













  

  

  
   <canvas class="fireworks" style="position: fixed;left: 0;top: 0;z-index: 1; pointer-events: none;" ></canvas> 
   <script type="text/javascript" src="//cdn.bootcss.com/animejs/2.2.0/anime.min.js"></script> 
   <script type="text/javascript" src="/js/fireworks.js"></script>
  
</body>
</html>

